Compare commits

..

45 Commits

Author SHA1 Message Date
Sun Jianbo
525c228bd7 Merge pull request #311 from wonderflow/fix
fix env exist but issuer not exist
2020-09-23 11:22:11 +08:00
天元
ba6a53c6f5 fix env exist but issuer not exist 2020-09-23 10:38:59 +08:00
Sun Jianbo
0d1dd62b8a Merge pull request #308 from wonderflow/init
vela init to create and run application in one command
2020-09-22 20:23:41 +08:00
天元
5ac7265474 vela init to create and run application in one command 2020-09-22 20:07:53 +08:00
Zheng Xi Zhou
08701568a1 Fix OpenAPIV3Schema Validation Issue (#300)
* Fix OpenAPIV3Schema Validation Issue

Temporarily corrects spec.validation.openAPIV3Schema issue, and it would be removed
after this issue was fixed https://github.com/oam-dev/kubevela/issues/284.

* fix vela install issue

* return error during fixing APIV3SchemaValidation issue
2020-09-22 15:51:38 +08:00
roy wang
faedce906a fix #128 | sync deleted workload locally
Signed-off-by: roy wang <seiwy2010@gmail.com>

fix & add unit tests

Signed-off-by: roy wang <seiwy2010@gmail.com>

fix info output & unit test

Signed-off-by: roy wang <seiwy2010@gmail.com>

fix e2e-test

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-09-21 19:22:34 +08:00
Sun Jianbo
f5908741d3 Update pkg/commands/system.go 2020-09-21 19:22:34 +08:00
mosesyou
5414cc1f86 fix duplicate vela system info failure message 2020-09-21 19:22:34 +08:00
天元
371a989f9b add move readme 2020-09-21 19:22:34 +08:00
天元
8b3af3be93 temporarily add containerized to chart 2020-09-21 19:22:34 +08:00
hanxie
d6e519f1c4 Components static page,bugfix,capability delete 2020-09-21 19:22:34 +08:00
Sun Jianbo
90fd0a5055 Merge pull request #295 from wonderflow/production-issuer
add production certificate issuer and fix route trait
2020-09-18 21:45:14 +08:00
天元
3af41f6515 add production certificate issuer and fix route trait 2020-09-18 19:06:51 +08:00
Yue Wang
ee39054537 get cue temp from remote through URI (#287)
* get cue template of capabilities from remote by URI

Signed-off-by: roy wang <seiwy2010@gmail.com>

* add e2e tests

Signed-off-by: roy wang <seiwy2010@gmail.com>

* fix type conversion

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-09-18 15:59:36 +08:00
Ryan Zhang
b5218d371a Merge pull request #293 from wonderflow/iamge
use image from docker hub instead of quay.io
2020-09-17 19:23:41 -07:00
天元
7f5298a802 use image from docker hub instead of quay.io 2020-09-17 17:30:33 +08:00
Sun Jianbo
2ffd56a993 Merge pull request #291 from wonderflow/zip
use compressed file for release
2020-09-17 15:07:00 +08:00
天元
e0d7eed9d3 update readme 2020-09-17 14:52:33 +08:00
Sun Jianbo
b0ec26bca0 Merge pull request #290 from wonderflow/image
update image tag to latest and change image pull policy
2020-09-17 14:44:19 +08:00
天元
2116c9ad0e use compressed file for release 2020-09-17 14:42:00 +08:00
天元
d02d6675ab update image tag to latest and change image pull policy 2020-09-17 14:08:33 +08:00
Sun Jianbo
1a43c0e540 Merge pull request #276 from wonderflow/routetrait
add route trait as vela api gateway
2020-09-17 14:06:00 +08:00
Sun Jianbo
e6b46c6c1b Merge pull request #288 from mosesyou/bugfix-comp-run
fix `vela comp run` panic when args empty
2020-09-17 11:57:27 +08:00
mosesyou
387afaa2c2 fix vela comp run panic when args empty 2020-09-17 11:08:55 +08:00
Ryan Zhang
f9fea8b53a Merge pull request #285 from wonderflow/dockerpackage
Add Image upload for Docker Hub and Github Docker package
2020-09-16 19:32:07 -07:00
Hongchao Deng
21c58c0aa2 Merge pull request #286 from oam-dev/warning
Add warning for not play around it for now
2020-09-16 16:43:43 -07:00
Lei Zhang (Harry)
d5a8b54503 Add warning for not play around it for now 2020-09-16 16:39:06 -07:00
天元
c390928368 split release and daily build 2020-09-16 22:57:56 +08:00
天元
c4897008dc add docker image build 2020-09-16 21:50:56 +08:00
天元
fe09a85e53 add docker build 2020-09-16 21:34:20 +08:00
天元
1b9ee5c882 add route trait as vela apigate 2020-09-16 19:42:01 +08:00
Sun Jianbo
cc5e3dc6a2 Merge pull request #281 from oam-dev/install-dependency
vela core install dependencies
2020-09-16 17:29:02 +08:00
Ryan Zhang
8a9470b9b3 fix build error 2020-09-16 01:12:50 -07:00
Ryan Zhang
e0a21b2bd4 Merge pull request #268 from wonderflow/template
update chart and refactor default workload type
2020-09-15 22:52:32 -07:00
Ryan Zhang
dfeff25a31 install dependencies 2020-09-15 22:04:52 -07:00
天元
9510db0ace update chart and refactor default workload 2020-09-16 12:09:29 +08:00
Sun Jianbo
1576d1ff2b Merge pull request #280 from hanxie-crypto/feature03
Update code and page optimization ,fix bug
2020-09-16 11:37:03 +08:00
hanxie
764e1bbb79 Update code and page optimization ,fix bug 2020-09-16 11:03:16 +08:00
Sun Jianbo
83e718c4cd Merge pull request #261 from hanxie-crypto/feature01
update dashboard ux
2020-09-16 10:50:13 +08:00
Ryan Zhang
16fd51a213 adjust the helm setting and README (#269)
* add dependancy installer and fix e2e

* fix build

* add kubebuilder
2020-09-15 15:08:17 +08:00
Sun Jianbo
1744b4752c Merge pull request #270 from zzxwill/website
Remove website related files
2020-09-13 11:44:31 +08:00
zzxwill
7ad7848856 Remove website related files
Removed website related files and planning moving
the website to oam-dev/kubevela.io
2020-09-13 11:29:44 +08:00
Sun Jianbo
6df4e192f9 Merge pull request #267 from zzxwill/env
Rename `vela env switch` to `vela env set`
2020-09-11 21:40:36 +08:00
zzxwill
bc3169a1b5 Rename vela env switch to vela env set
Renaming and update cli documentation
Fix #235
2020-09-11 19:46:45 +08:00
hanxie
b026cf20f4 update dashboard ux 2020-09-11 14:43:08 +08:00
247 changed files with 7761 additions and 3640 deletions

25
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Publish to Registry
on:
push:
branches:
- master
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Publish to Github Docker Package Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:
name: oam-dev/kubevela/vela-core
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: docker.pkg.github.com
tags: "latest"
- name: Publish to Docker Hub Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:
name: oamdev/vela-core
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
tags: "latest"

View File

@@ -34,9 +34,15 @@ jobs:
with:
version: "v0.7.0"
- name: install Kubebuilder
uses: RyanSiu1995/kubebuilder-action@v1
- name: Run Make
run: make
- name: Run Make Manager
run: make manager
- name: Run e2e tests
run: |
make e2e-setup

View File

@@ -30,5 +30,8 @@ jobs:
with:
version: "v0.7.0"
- name: install Kubebuilder
uses: RyanSiu1995/kubebuilder-action@v1
- name: Run Make test
run: make test

View File

@@ -32,6 +32,8 @@ jobs:
run: make generate-source
- name: Run cross-build
run: make cross-build
- name: Run compress binary
run: make compress
- name: Create Release
id: create_release
uses: actions/create-release@v1
@@ -41,24 +43,67 @@ jobs:
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Upload Linux
- name: Upload Linux tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-linux-amd64
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64
asset_path: ./_bin/vela-linux-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload MacOS
- name: Upload Linux zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-darwin-amd64
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64
asset_path: ./_bin/vela-linux-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Windows
- name: Upload MacOS tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-windows-amd64.exe
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.exe
asset_content_type: binary/octet-stream
asset_path: ./_bin/vela-darwin-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload MacOS zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-darwin-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Windows tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-windows-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload Windows zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-windows-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Checksums
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/sha256sums.txt
asset_name: sha256sums.txt
asset_content_type: text/plain
- name: Publish to Github Docker Package Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:
name: oam-dev/kubevela/vela-core
username: $GITHUB_ACTOR
password: ${{ secrets.GITHUB_TOKEN }}
registry: docker.pkg.github.com
tags: "${{ steps.get_version.outputs.VERSION }}"
- name: Publish to Docker Hub Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:
name: oamdev/vela-core
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
tags: "${{ steps.get_version.outputs.VERSION }}"

5
.gitignore vendored
View File

@@ -5,6 +5,7 @@
*.so
*.dylib
bin
_bin
e2e/vela
# Test binary, build with `go test -c`
@@ -35,6 +36,7 @@ config/crd/bases
tmp/
cmd/vela/fake/source.go
charts/vela-core/crds/_.yaml
# Dashboard
dashboard/node_modules/
@@ -44,3 +46,6 @@ dashboard/package-lock.json
dashboard/src/.umi/
package-lock.json
dashboard/src/.umi-production/
# build
charts/vela

View File

@@ -5,12 +5,10 @@ contributing to `kubevela` or build a PoC (Proof of Concept).
## Prerequisites
1. Golang version 1.12+
2. Kubernetes version v1.15+ with `~/.kube/config` configured.
3. OAM Kubernetes Runtime installed.
4. Kustomize version 3.8+
5. ginkgo 1.14.0+ (just for [E2E test](https://github.com/oam-dev/kubevela/blob/master/DEVELOPMENT.md#e2e-test))
6. golangci-lint 1.31.0+, it will install automatically if you run `make`, you can [install it manually](https://golangci-lint.run/usage/install/#local-installation) if the installation is too slow.
1. Golang version 1.13+
2. Kubernetes version v1.16+ with `~/.kube/config` configured.
3. ginkgo 1.14.0+ (just for [E2E test](https://github.com/oam-dev/kubevela/blob/master/DEVELOPMENT.md#e2e-test))
4. golangci-lint 1.31.0+, it will install automatically if you run `make`, you can [install it manually](https://golangci-lint.run/usage/install/#local-installation) if the installation is too slow.
## Build
* Clone this project
@@ -19,96 +17,89 @@ contributing to `kubevela` or build a PoC (Proof of Concept).
git clone git@github.com:oam-dev/kubevela.git
```
* Install Template CRD into your cluster
* Build Vela CLI
```shell script
make install
make
```
* Install template object
* Configure vela to PATH
after build, make will create `vela` binary to `bin/`, Set this path to PATH.
```shell script
kubectl apply -f config/samples/
export PATH=$PATH:/your/path/to/project/kubevela/bin
```
## Develop & Debug
If you change Template CRD, remember to rerun `make install`.
Use the following command to develop and debug.
* Install Vela Core
```shell script
$ cd cmd/vela
$ go run main.go COMMAND [FLAG]
vela install
```
## Use
* Create ENV
```shell script
vela env init myenv --namespace myenv --email my@email.com --domain kubevela.io
```
* Create Component
For example, use the following command to create and run an application.
```shell script
$ go run main.go run containerized app2057 nginx:1.9.4
Creating AppConfig app2057
$ vela comp run mycomp -t webservice --image crccheck/hello-world --port 8000
Creating AppConfig appcomp
SUCCEED
$ kubectl get oam
NAME WORKLOAD-KIND
component.core.oam.dev/app2057 ContainerizedWorkload
NAME AGE
containerizedworkload.core.oam.dev/poc 53m
NAME AGE
applicationconfiguration.core.oam.dev/app2057 69s
NAME DEFINITION-NAME
traitdefinition.core.oam.dev/simplerollouttraits.extend.oam.dev simplerollouttraits.extend.oam.dev
NAME DEFINITION-NAME
workloaddefinition.core.oam.dev/containerizedworkloads.core.oam.dev containerizedworkloads.core.oam.dev
workloaddefinition.core.oam.dev/deployments.apps deployments.apps
workloaddefinition.core.oam.dev/statefulsets.apps statefulsets.apps
```
## E2E test
```
$ make e2e-test
Running Suite: Trait Suite
==========================
Random Seed: 1596559178
Will run 5 of 5 specs
* Add Trait
Trait env init
should print env initiation successful message
kubevela/e2e/commonContext.go:14
Create env succeed, current env is default
------------------------------
Trait env switch
should show env switch message
kubevela/e2e/commonContext.go:40
Switch env succeed, current env is default
------------------------------
Trait run
should print successful creation information
kubevela/e2e/commonContext.go:76
Creating AppConfig app-trait-basic
SUCCEED
------------------------------
Trait kubevela attach trait
should print successful attached information
kubevela/e2e/trait/trait_test.go:24
Applying trait for app
```shell script
$ vela route mycomp
Adding route for app abc
Succeeded!
------------------------------
Trait delete
should print successful deletion information
kubevela/e2e/commonContext.go:85
Deleting AppConfig "app-trait-basic"
DELETE SUCCEED
Ran 5 of 5 Specs in 9.717 seconds
SUCCESS! -- 5 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS
```
* Check Status
```
$ vela comp status abc
Showing status of Component abc deployed in Environment t2
Component Status:
Name: abc Containerized(type) UNKNOWN APIVersion standard.oam.dev/v1alpha1 Kind Containerized workload is unknown for HealthScope
Traits
└─Trait/route
Last Deployment:
Created at: 2020-09-18 18:47:09 +0800 CST
Updated at: 2020-09-18T18:47:16+08:00
```
* Delete App
```shell script
$ vela app ls
abc
$ vela app delete abc
Deleting Application "abc"
delete apps succeed abc from t2
```
## Tests
### Unit test
```shell script
make test
```
### E2E test
```
make e2e-test
```
## Make a pull request

View File

@@ -8,6 +8,7 @@ LDFLAGS ?= "-X $(VELA_VERSION_VAR)=$(VELA_VERSION) -X $(VELA_GITVERSION_VAR)=$(G
GOX = go run github.com/mitchellh/gox
TARGETS := darwin/amd64 linux/amd64 windows/amd64
DIST_DIRS := find * -type d -exec
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
@@ -36,7 +37,17 @@ generate-source:
go run hack/frontend/source.go
cross-build:
go run hack/chart/generate.go | GO111MODULE=on CGO_ENABLED=0 $(GOX) -ldflags $(LDFLAGS) -parallel=3 -output="bin/vela-{{.OS}}-{{.Arch}}" -osarch='$(TARGETS)' ./cmd/vela/
go run hack/chart/generate.go | GO111MODULE=on CGO_ENABLED=0 $(GOX) -ldflags $(LDFLAGS) -parallel=3 -output="_bin/{{.OS}}-{{.Arch}}/vela" -osarch='$(TARGETS)' ./cmd/vela/
compress:
( \
cd _bin && \
$(DIST_DIRS) cp ../LICENSE {} \; && \
$(DIST_DIRS) cp ../README.md {} \; && \
$(DIST_DIRS) tar -zcf vela-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r vela-{}.zip {} \; && \
sha256sum vela-* > sha256sums.txt \
)
# Run against the configured Kubernetes cluster in ~/.kube/config
run: fmt vet
@@ -51,7 +62,7 @@ vet:
go vet ./...
lint: golangci
$(GOLANGCILINT) run -E golint,goimports ./...
$(GOLANGCILINT) run --timeout 10m -E golint,goimports ./...
# Build the docker image
docker-build: test
@@ -88,7 +99,7 @@ core-test: generate fmt vet manifests
go test ./pkg/... -coverprofile cover.out
# Build manager binary
manager: generate fmt vet
manager: generate fmt vet lint manifests
go build -o bin/manager ./cmd/core/main.go
# Run against the configured Kubernetes cluster in ~/.kube/config
@@ -110,7 +121,7 @@ core-deploy: manifests
# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=charts/vela/crds
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=charts/vela-core/crds
# Generate code
generate: controller-gen

View File

@@ -1,3 +1,7 @@
domain: oam.dev
repo: github.com/oam-dev/kubevela
resources:
- group: standard
kind: Route
version: v1alpha1
version: "2"

291
README.md
View File

@@ -2,6 +2,8 @@
The Open Application Platform based on Kubernetes and OAM.
:rotating_light: **Warning: The project is still under heavy development, its UI/UX is also for demo purpose, please don't look inside unless you know what you are doing** Please contact @wonderflow if you are interested in its full story or becoming one of the boostrap contributors/maintainers. :rotating_light:
## Install
### Prerequisites
@@ -11,80 +13,265 @@ The Open Application Platform based on Kubernetes and OAM.
### Get the Vela CLI
Download the `vela` binary from the [Releases page](https://github.com/oam-dev/kubevela/releases). Change file mod and add it to `$PATH` to get started.
Download the `vela` binary from the [Releases page](https://github.com/oam-dev/kubevela/releases). Unpack the `vela` binary and add it to `$PATH` to get started.
For exmaple:
```shell
chmod a+x vela-v0.0.2-darwin-amd64
sudo mv ./vela-v0.0.2-darwin-amd64 /usr/local/bin/vela
sudo mv ./vela /usr/local/bin/vela
```
### Install Vela Core
```shell script
$ vela install
- Installing Vela Core:
- Installing builtin capabilities:
Successful applied 4 kinds of Workloads and Traits: deployments.apps,containerizedworkloads.core.oam.dev,manualscalertraits.core.oam.dev,simplerollouttraits.extend.oam.dev.
syncing workload definitions from cluster...
[WARN]handle template task: #Template.metadata.name: reference "task" not found
get 5 workload definitions from cluster, syncing...5 workload definitions successfully synced
syncing trait definitions from cluster...
[WARN]handle template metricstraits.standard.oam.dev: #Template.metadata.name: reference "metricstraits" not found
get 2 trait definitions from cluster, syncing...2 trait definitions successfully synced
- Finished.
```
This command will install vela core controller into your K8s cluster, along with built-in workloads and traits.
## Demos
#### Create ENV
After `vela install` you will have workloads and traits locally, and available to use by vela cli.
```
$ vela env init test --namespace test
Create env succeed, current env is test
$ vela env ls
NAME CURRENT NAMESPACE
default default
test * test
$ vela env switch default
Switch env succeed, current env is default
$ vela env delete test
test deleted
$ vela env delete default
Error: you can't delete current using default
```
#### workload run
```shell script
$ vela comp run -t deployment app123 -p 80 --image nginx:1.9.4
Creating AppConfig app123
$ vela workloads
NAME DEFINITION
backend containerizeds.standard.oam.dev
containerized containerizedworkloads.core.oam.dev
task jobs
webservice containerizeds.standard.oam.dev
```
```shell script
$ vela traits
NAME DEFINITION APPLIES TO
route routes.standard.oam.dev webservice
backend
scale manualscalertraits.core.oam.dev webservice
backend
```
### Create ENV
Before working with your application, you should create an env for it.
```shell script
$ vela env init myenv --namespace myenv --email my@email.com --domain kubevela.io
ENV myenv CREATED, Namespace: myenv, Email: my@email.com.
```
It will create a namespace called myenv
```shell script
$ kubectl get ns
NAME STATUS AGE
myenv Active 40s
```
A namespace level issuer for certificate generation with email.
```shell script
$ kubectl get issuers.cert-manager.io -n myenv
NAME READY AGE
oam-env-myenv True 40s
```
A env metadata in your local:
```shell script
$ cat ~/.vela/envs/myenv/config.json
{"name":"myenv","namespace":"myenv","email":"my@email.com","domain":"kubevela.io","issuer":"oam-env-myenv"}
```
### Create Component
Then let's create application, we will use our env created by default.
```shell script
$ vela comp run mycomp -t webservice --image crccheck/hello-world --port 8000 --app myapp
Creating AppConfig appcomp
SUCCEED
$ vela comp status app123
```
#### app
It will create component named `mycomp`.
```shell script
$ kubectl get components -n myenv
NAME WORKLOAD-KIND AGE
mycomp Containerized 10s
```
$ vela app ls
app123
poc08032042
poc1039
And an AppConfig named myapp.
```shell script
$ kubectl get appconfig -n myenv
NAME AGE
myapp 24s
```
Vela Core will work for AppConfig and create K8s deployment and service.
```shell script
$ kubectl get deployment -n myenv
NAME READY UP-TO-DATE AVAILABLE AGE
mycomp 1/1 1 1 38s
```
```shell script
$ kubectl get svc -n myenv
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mycomp ClusterIP 172.21.4.228 <none> 8080/TCP 49s
```
### Multiple Component
Creating a new component in the same application is easy, just use the `--app` flag.
```shell script
$ vela comp run db -t backend --image crccheck/hello-world --app myapp
Creating App myapp
SUCCEED
```
```shell script
$ vela comp ls
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
ccc ccc deployment Deployed 2020-08-27 10:56:41 +0800 CST
com1 com1 Deployed 2020-08-26 16:45:50 +0800 CST
com2 com1 Deployed 2020-08-26 16:45:50 +0800 CST
myapp myapp route,scale Deployed 2020-08-19 15:11:17 +0800 CST
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
db myapp backend Deployed 2020-09-18 22:42:04 +0800 CST
mycomp myapp webservice Deployed 2020-09-18 22:42:04 +0800 CST
```
$ vela app delete app123
Deleting AppConfig "app123"
DELETE SUCCEED
Now we can see the application deployed, let's add route trait for visiting.
### Add Trait
```shell script
$ vela route mycomp --app myapp
Adding route for app mycomp
Succeeded!
```
It will create route trait for this component.
```shell script
$ kubeclt get routes.standard.oam.dev -n myenv
NAME AGE
mycomp-trait-5b576c4fc 18s
```
Controller of route trait which is part of vela core will create an ingress for it.
```shell script
$ kubectl get ingress -n myenv
NAME HOSTS ADDRESS PORTS AGE
mycomp-trait-5b576c4fc mycomp.kubevela.io 123.57.10.233 80, 443 73s
```
Please configure your domain pointing to the public address.
Then you will be able to visit it by `https://mycomp.kubevela.io`, `mTLS` is automatically enabled.
### Check Status
App level:
```shell script
$ vela app show myapp
About:
Name: myapp
Created at: 2020-09-18 22:42:04.191171 +0800 CST
Updated at: 2020-09-18 22:51:11.128997 +0800 CST
Environment:
Namespace: myenv
Components:
Name Type Traits
db backend
mycomp webservice route
```
Component Level:
```shell script
$ vela comp show mycomp
About:
Name: mycomp
WorkloadType: webservice
Application: myapp
Environment:
Namespace: myenv
Arguments:
image: crccheck/hello-world
name: mycomp
port: 8000
Traits:
route:
domain: mycomp.kubevela.io
issuer: oam-env-myenv
name: route
```
```
$ vela comp status mycomp
Showing status of Component mycomp deployed in Environment myenv
Component Status:
Name: mycomp Containerized(type) UNKNOWN APIVersion standard.oam.dev/v1alpha1 Kind Containerized workload is unknown for HealthScope
Traits
└─Trait/route
Last Deployment:
Created at: 2020-09-18 22:42:04 +0800 CST
Updated at: 2020-09-18T22:51:11+08:00
```
### Delete App or Component
```shell script
$ vela app ls
myapp
```
```shell script
$ vela comp ls
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
db myapp backend Deployed 2020-09-18 22:42:04 +0800 CST
mycomp myapp webservice route Deployed 2020-09-18 22:42:04 +0800 CST
```
```shell script
$ vela comp delete db
Deleting Component 'db' from Application 'db'
```
```shell script
$ vela comp ls
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
mycomp myapp webservice route Deployed 2020-09-18 22:42:04 +0800 CST
```
```shell script
$ vela app delete myapp
Deleting Application "myapp"
delete apps succeed myapp from myenv
```
## Dashboard
We also prepared a dashboard for you, but it's still in heavily development.
```shell script
$ vela dashboard
```
#### Auto-Completion

View File

@@ -40,6 +40,7 @@ type Capability struct {
Name string `json:"name"`
Type CapType `json:"type"`
CueTemplate string `json:"template,omitempty"`
CueTemplateURI string `json:"templateURI,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
DefinitionPath string `json:"definition"`
CrdName string `json:"crdName,omitempty"`
@@ -56,10 +57,11 @@ type Capability struct {
}
type Chart struct {
Repo string `json:"repo"`
URL string `json:"url"`
Name string `json:"name"`
Version string `json:"version"`
Repo string `json:"repo"`
URL string `json:"url"`
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
Version string `json:"version"`
}
type Installation struct {

View File

@@ -27,8 +27,13 @@ const (
type EnvMeta struct {
Name string `json:"name"`
Current string `json:"current,omitempty"`
Namespace string `json:"namespace"`
Email string `json:"email,omitempty"`
Domain string `json:"domain,omitempty"`
// Below are not arguments, should be auto-generated
Issuer string `json:"issuer"`
Current string `json:"current,omitempty"`
}
const (

138
api/v1alpha1/route_types.go Normal file
View File

@@ -0,0 +1,138 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
"k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// RouteSpec defines the desired state of Route
type RouteSpec struct {
// WorkloadReference to the workload whose metrics needs to be exposed
WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
// Host is the host of the route
Host string `json:"host"`
// Path is location Path, default for "/"
Path string `json:"path,omitempty"`
// TLS indicate route trait will create SSL secret using cert-manager with specified issuer
// If this is nil, route trait will use a selfsigned issuer
TLS *TLS `json:"tls,omitempty"`
// DefaultBackend uses serviceName
DefaultBackend *v1beta1.IngressBackend `json:"defaultBackend,omitempty"`
// RewriteTarget will rewrite request from Path to RewriteTarget path.
RewriteTarget string `json:"rewriteTarget,omitempty"`
// CustomHeaders pass a custom list of headers to the backend service.
CustomHeaders map[string]string `json:"customHeaders,omitempty"`
// Backend indicate how to connect backend service
// If it's nil, will auto discovery
Backend *Backend `json:"backend,omitempty"`
}
type TLS struct {
IssuerName string `json:"issuerName,omitempty"`
// Type indicate the issuer is ClusterIssuer or NamespaceIssuer
Type IssuerType `json:"type,omitempty"`
}
type IssuerType string
const (
ClusterIssuer IssuerType = "ClusterIssuer"
NamespaceIssuer IssuerType = "Issuer"
)
// Route will automatically discover podTemplate for Port and SelectLabels if they are not set.
// If Port and SelectLabels are already set, discovery won't work.
// If Port is not set, the first port discovered will be set.
// If SelectLabels are not set, all selectorLabels discovered will be set.
type Backend struct {
// Protocol means backend-protocol, HTTP, HTTPS, GRPC, GRPCS, AJP and FCGI, By default uses HTTP
Protocol string `json:"protocol,omitempty"`
// ReadTimeout used for setting read timeout duration for backend service, the unit is second.
ReadTimeout int `json:"readTimeout,omitempty"`
// SendTimeout used for setting send timeout duration for backend service, the unit is second.
SendTimeout int `json:"sendTimeout,omitempty"`
// Port points to backend service port.
Port intstr.IntOrString `json:"port,omitempty"`
// SelectLabels for backend service.
SelectLabels map[string]string `json:"selectLabels,omitempty"`
}
// RouteStatus defines the observed state of Route
type RouteStatus struct {
Ingress *runtimev1alpha1.TypedReference `json:"ingress,omitempty"`
Service *runtimev1alpha1.TypedReference `json:"service,omitempty"`
runtimev1alpha1.ConditionedStatus `json:",inline"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:categories={oam}
// +kubebuilder:subresource:status
// Route is the Schema for the routes API
type Route struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec RouteSpec `json:"spec,omitempty"`
Status RouteStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// RouteList contains a list of Route
type RouteList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Route `json:"items"`
}
func init() {
SchemeBuilder.Register(&Route{}, &RouteList{})
}
var _ oam.Trait = &Route{}
func (r *Route) SetConditions(c ...runtimev1alpha1.Condition) {
r.Status.SetConditions(c...)
}
func (r *Route) GetCondition(c runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
return r.Status.GetCondition(c)
}
// GetWorkloadReference of this ManualScalerTrait.
func (r *Route) GetWorkloadReference() runtimev1alpha1.TypedReference {
return r.Spec.WorkloadReference
}
// SetWorkloadReference of this ManualScalerTrait.
func (r *Route) SetWorkloadReference(rt runtimev1alpha1.TypedReference) {
r.Spec.WorkloadReference = rt
}

View File

@@ -22,9 +22,33 @@ package v1alpha1
import (
corev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"k8s.io/api/networking/v1beta1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Backend) DeepCopyInto(out *Backend) {
*out = *in
out.Port = in.Port
if in.SelectLabels != nil {
in, out := &in.SelectLabels, &out.SelectLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend.
func (in *Backend) DeepCopy() *Backend {
if in == nil {
return nil
}
out := new(Backend)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Containerized) DeepCopyInto(out *Containerized) {
*out = *in
@@ -223,6 +247,129 @@ func (in *MetricsTraitStatus) DeepCopy() *MetricsTraitStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Route) DeepCopyInto(out *Route) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route.
func (in *Route) DeepCopy() *Route {
if in == nil {
return nil
}
out := new(Route)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Route) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteList) DeepCopyInto(out *RouteList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Route, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteList.
func (in *RouteList) DeepCopy() *RouteList {
if in == nil {
return nil
}
out := new(RouteList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RouteList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteSpec) DeepCopyInto(out *RouteSpec) {
*out = *in
out.WorkloadReference = in.WorkloadReference
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLS)
**out = **in
}
if in.DefaultBackend != nil {
in, out := &in.DefaultBackend, &out.DefaultBackend
*out = new(v1beta1.IngressBackend)
(*in).DeepCopyInto(*out)
}
if in.CustomHeaders != nil {
in, out := &in.CustomHeaders, &out.CustomHeaders
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Backend != nil {
in, out := &in.Backend, &out.Backend
*out = new(Backend)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteSpec.
func (in *RouteSpec) DeepCopy() *RouteSpec {
if in == nil {
return nil
}
out := new(RouteSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteStatus) DeepCopyInto(out *RouteStatus) {
*out = *in
if in.Ingress != nil {
in, out := &in.Ingress, &out.Ingress
*out = new(corev1alpha1.TypedReference)
**out = **in
}
if in.Service != nil {
in, out := &in.Service, &out.Service
*out = new(corev1alpha1.TypedReference)
**out = **in
}
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteStatus.
func (in *RouteStatus) DeepCopy() *RouteStatus {
if in == nil {
return nil
}
out := new(RouteStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScapeServiceEndPoint) DeepCopyInto(out *ScapeServiceEndPoint) {
*out = *in
@@ -250,3 +397,18 @@ func (in *ScapeServiceEndPoint) DeepCopy() *ScapeServiceEndPoint {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLS) DeepCopyInto(out *TLS) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS.
func (in *TLS) DeepCopy() *TLS {
if in == nil {
return nil
}
out := new(TLS)
in.DeepCopyInto(out)
return out
}

View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Copyright 2019 The Knative Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env bash
# Download and unpack cert-manager
CERT_MANAGER_VERSION=1.0.0
ARCHIVE_DOWNLOAD_URL=https://github.com/jetstack/cert-manager/archive/v${CERT_MANAGER_VERSION}.tar.gz
YAML_URL=https://github.com/jetstack/cert-manager/releases/download/v${CERT_MANAGER_VERSION}/cert-manager.yaml
wget $ARCHIVE_DOWNLOAD_URL
tar xzf v${CERT_MANAGER_VERSION}.tar.gz
(
# subshell in downloaded directory
cd cert-manager-${CERT_MANAGER_VERSION} || exit
# Copy the CRD yaml file
cp deploy/manifests/00-crds.yaml ../cert-manager-crds.yaml
)
# Download the cert-manager yaml file
wget $YAML_URL
# Clean up.
rm -rf cert-manager-${CERT_MANAGER_VERSION}
rm v${CERT_MANAGER_VERSION}.tar.gz
# Add enable-certificate-owner-ref option to cert-manager's controller.
# The option is to cleans up secret(certificate) by adding ownerref.
patch -l cert-manager.yaml owner-ref.patch

View File

@@ -11,124 +11,190 @@ spec:
group: core.oam.dev
names:
categories:
- crossplane
- oam
- crossplane
- oam
kind: HealthScope
listKind: HealthScopeList
plural: healthscopes
singular: healthscope
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.health
name: HEALTH
type: string
name: v1alpha2
schema:
openAPIV3Schema:
description: A HealthScope determines an aggregate health status based of
the health of components.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
- additionalPrinterColumns:
- jsonPath: .status.health
name: HEALTH
type: string
name: v1alpha2
schema:
openAPIV3Schema:
description: A HealthScope determines an aggregate health status based of
the health of components.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: A HealthScopeSpec defines the desired state of a HealthScope.
properties:
probe-interval:
description: ProbeInterval is the amount of time in seconds between
probing tries.
format: int32
type: integer
probe-timeout:
description: ProbeTimeout is the amount of time in seconds to wait
when receiving a response before marked failure.
format: int32
type: integer
workloadRefs:
description: WorkloadReferences to the workloads that are in this
scope.
items:
description: A TypedReference refers to an object by Name, Kind,
and APIVersion. It is commonly used to reference cluster-scoped
objects or objects where the namespace is already known.
type: string
metadata:
type: object
spec:
description: A HealthScopeSpec defines the desired state of a HealthScope.
properties:
probe-interval:
description: ProbeInterval is the amount of time in seconds between
probing tries.
format: int32
type: integer
probe-timeout:
description: ProbeTimeout is the amount of time in seconds to wait
when receiving a response before marked failure.
format: int32
type: integer
workloadRefs:
description: WorkloadReferences to the workloads that are in this
scope.
items:
description: A TypedReference refers to an object by Name, Kind,
and APIVersion. It is commonly used to reference cluster-scoped
objects or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: array
required:
- workloadRefs
type: object
status:
description: A HealthScopeStatus represents the observed state of a HealthScope.
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
healthConditions:
description: WorkloadHealthConditions represents health condition
of workloads in the scope
items:
description: WorkloadHealthCondition represents informative health
condition.
properties:
componentName:
description: ComponentName represents the component name if
target is a workload
type: string
diagnosis:
type: string
healthStatus:
description: HealthStatus represents health status strings.
type: string
targetWorkload:
description: A TypedReference refers to an object by Name, Kind,
and APIVersion. It is commonly used to reference cluster-scoped
objects or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
workloadStatus:
description: WorkloadStatus represents status of workloads whose
HealthStatus is UNKNOWN.
type: string
required:
- healthStatus
type: object
type: array
scopeHealthCondition:
description: ScopeHealthCondition represents health condition summary
of the scope
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
healthStatus:
description: HealthStatus represents health status strings.
type: string
healthyWorkloads:
format: int64
type: integer
total:
format: int64
type: integer
unhealthyWorkloads:
format: int64
type: integer
unknownWorkloads:
format: int64
type: integer
required:
- apiVersion
- kind
- name
- healthStatus
type: object
type: array
required:
- workloadRefs
type: object
status:
description: A HealthScopeStatus represents the observed state of a HealthScope.
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
health:
type: string
required:
- health
type: object
type: object
served: true
storage: true
subresources:
status: {}
required:
- scopeHealthCondition
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -0,0 +1,465 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.4
creationTimestamp: null
name: servicemonitors.monitoring.coreos.com
spec:
group: monitoring.coreos.com
names:
kind: ServiceMonitor
listKind: ServiceMonitorList
plural: servicemonitors
singular: servicemonitor
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: ServiceMonitor defines monitoring for a set of services.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Specification of desired Service selection for target discovery
by Prometheus.
properties:
endpoints:
description: A list of endpoints allowed as part of this ServiceMonitor.
items:
description: Endpoint defines a scrapeable endpoint serving Prometheus
metrics.
properties:
basicAuth:
description: 'BasicAuth allow an endpoint to authenticate over
basic authentication More info: https://prometheus.io/docs/operating/configuration/#endpoints'
properties:
password:
description: The secret in the service monitor namespace
that contains the password for authentication.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
username:
description: The secret in the service monitor namespace
that contains the username for authentication.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
type: object
bearerTokenFile:
description: File to read bearer token for scraping targets.
type: string
bearerTokenSecret:
description: Secret to mount to read bearer token for scraping
targets. The secret needs to be in the same namespace as the
service monitor and accessible by the Prometheus Operator.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
honorLabels:
description: HonorLabels chooses the metric's labels on collisions
with target labels.
type: boolean
honorTimestamps:
description: HonorTimestamps controls whether Prometheus respects
the timestamps present in scraped data.
type: boolean
interval:
description: Interval at which metrics should be scraped
type: string
metricRelabelings:
description: MetricRelabelConfigs to apply to samples before
ingestion.
items:
description: 'RelabelConfig allows dynamic rewriting of the
label set, being applied to samples before ingestion. It
defines `<metric_relabel_configs>`-section of Prometheus
configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs'
properties:
action:
description: Action to perform based on regex matching.
Default is 'replace'
type: string
modulus:
description: Modulus to take of the hash of the source
label values.
format: int64
type: integer
regex:
description: Regular expression against which the extracted
value is matched. Default is '(.*)'
type: string
replacement:
description: Replacement value against which a regex replace
is performed if the regular expression matches. Regex
capture groups are available. Default is '$1'
type: string
separator:
description: Separator placed between concatenated source
label values. default is ';'.
type: string
sourceLabels:
description: The source labels select values from existing
labels. Their content is concatenated using the configured
separator and matched against the configured regular
expression for the replace, keep, and drop actions.
items:
type: string
type: array
targetLabel:
description: Label to which the resulting value is written
in a replace action. It is mandatory for replace actions.
Regex capture groups are available.
type: string
type: object
type: array
params:
additionalProperties:
items:
type: string
type: array
description: Optional HTTP URL parameters
type: object
path:
description: HTTP path to scrape for metrics.
type: string
port:
description: Name of the service port this endpoint refers to.
Mutually exclusive with targetPort.
type: string
proxyUrl:
description: ProxyURL eg http://proxyserver:2195 Directs scrapes
to proxy through this endpoint.
type: string
relabelings:
description: 'RelabelConfigs to apply to samples before scraping.
More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config'
items:
description: 'RelabelConfig allows dynamic rewriting of the
label set, being applied to samples before ingestion. It
defines `<metric_relabel_configs>`-section of Prometheus
configuration. More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs'
properties:
action:
description: Action to perform based on regex matching.
Default is 'replace'
type: string
modulus:
description: Modulus to take of the hash of the source
label values.
format: int64
type: integer
regex:
description: Regular expression against which the extracted
value is matched. Default is '(.*)'
type: string
replacement:
description: Replacement value against which a regex replace
is performed if the regular expression matches. Regex
capture groups are available. Default is '$1'
type: string
separator:
description: Separator placed between concatenated source
label values. default is ';'.
type: string
sourceLabels:
description: The source labels select values from existing
labels. Their content is concatenated using the configured
separator and matched against the configured regular
expression for the replace, keep, and drop actions.
items:
type: string
type: array
targetLabel:
description: Label to which the resulting value is written
in a replace action. It is mandatory for replace actions.
Regex capture groups are available.
type: string
type: object
type: array
scheme:
description: HTTP scheme to use for scraping.
type: string
scrapeTimeout:
description: Timeout after which the scrape is ended
type: string
targetPort:
anyOf:
- type: integer
- type: string
description: Name or number of the pod port this endpoint refers
to. Mutually exclusive with port.
x-kubernetes-int-or-string: true
tlsConfig:
description: TLS configuration to use when scraping the endpoint
properties:
ca:
description: Stuct containing the CA cert to use for the
targets.
properties:
configMap:
description: ConfigMap containing data to use for the
targets.
properties:
key:
description: The key to select.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind,
uid?'
type: string
optional:
description: Specify whether the ConfigMap or its
key must be defined
type: boolean
required:
- key
type: object
secret:
description: Secret containing data to use for the targets.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind,
uid?'
type: string
optional:
description: Specify whether the Secret or its key
must be defined
type: boolean
required:
- key
type: object
type: object
caFile:
description: Path to the CA cert in the Prometheus container
to use for the targets.
type: string
cert:
description: Struct containing the client cert file for
the targets.
properties:
configMap:
description: ConfigMap containing data to use for the
targets.
properties:
key:
description: The key to select.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind,
uid?'
type: string
optional:
description: Specify whether the ConfigMap or its
key must be defined
type: boolean
required:
- key
type: object
secret:
description: Secret containing data to use for the targets.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind,
uid?'
type: string
optional:
description: Specify whether the Secret or its key
must be defined
type: boolean
required:
- key
type: object
type: object
certFile:
description: Path to the client cert file in the Prometheus
container for the targets.
type: string
insecureSkipVerify:
description: Disable target certificate validation.
type: boolean
keyFile:
description: Path to the client key file in the Prometheus
container for the targets.
type: string
keySecret:
description: Secret containing the client key file for the
targets.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
serverName:
description: Used to verify the hostname for the targets.
type: string
type: object
type: object
type: array
jobLabel:
description: The label to use to retrieve the job name from.
type: string
namespaceSelector:
description: Selector to select which namespaces the Endpoints objects
are discovered from.
properties:
any:
description: Boolean describing whether all namespaces are selected
in contrast to a list restricting them.
type: boolean
matchNames:
description: List of namespace names.
items:
type: string
type: array
type: object
podTargetLabels:
description: PodTargetLabels transfers labels on the Kubernetes Pod
onto the target.
items:
type: string
type: array
sampleLimit:
description: SampleLimit defines per-scrape limit on number of scraped
samples that will be accepted.
format: int64
type: integer
selector:
description: Selector to select Endpoints objects.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
The requirements are ANDed.
items:
description: A label selector requirement is a selector that
contains values, a key, and an operator that relates the key
and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: operator represents a key's relationship to
a set of values. Valid operators are In, NotIn, Exists
and DoesNotExist.
type: string
values:
description: values is an array of string values. If the
operator is In or NotIn, the values array must be non-empty.
If the operator is Exists or DoesNotExist, the values
array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single
{key,value} in the matchLabels map is equivalent to an element
of matchExpressions, whose key field is "key", the operator
is "In", and the values array contains only "value". The requirements
are ANDed.
type: object
type: object
targetLabels:
description: TargetLabels transfers labels on the Kubernetes Service
onto the target.
items:
type: string
type: array
required:
- endpoints
- selector
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,245 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: routes.standard.oam.dev
spec:
group: standard.oam.dev
names:
categories:
- oam
kind: Route
listKind: RouteList
plural: routes
singular: route
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Route is the Schema for the routes API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: RouteSpec defines the desired state of Route
properties:
backend:
description: Backend indicate how to connect backend service If it's
nil, will auto discovery
properties:
port:
anyOf:
- type: integer
- type: string
description: Port points to backend service port.
x-kubernetes-int-or-string: true
protocol:
description: Protocol means backend-protocol, HTTP, HTTPS, GRPC,
GRPCS, AJP and FCGI, By default uses HTTP
type: string
readTimeout:
description: ReadTimeout used for setting read timeout duration
for backend service, the unit is second.
type: integer
selectLabels:
additionalProperties:
type: string
description: SelectLabels for backend service.
type: object
sendTimeout:
description: SendTimeout used for setting send timeout duration
for backend service, the unit is second.
type: integer
type: object
customHeaders:
additionalProperties:
type: string
description: CustomHeaders pass a custom list of headers to the backend
service.
type: object
defaultBackend:
description: DefaultBackend uses serviceName
properties:
resource:
description: Resource is an ObjectRef to another Kubernetes resource
in the namespace of the Ingress object. If resource is specified,
serviceName and servicePort must not be specified.
properties:
apiGroup:
description: APIGroup is the group for the resource being
referenced. If APIGroup is not specified, the specified
Kind must be in the core API group. For any other third-party
types, APIGroup is required.
type: string
kind:
description: Kind is the type of resource being referenced
type: string
name:
description: Name is the name of resource being referenced
type: string
required:
- kind
- name
type: object
serviceName:
description: Specifies the name of the referenced service.
type: string
servicePort:
anyOf:
- type: integer
- type: string
description: Specifies the port of the referenced service.
x-kubernetes-int-or-string: true
type: object
host:
description: Host is the host of the route
type: string
path:
description: Path is location Path, default for "/"
type: string
rewriteTarget:
description: RewriteTarget will rewrite request from Path to RewriteTarget
path.
type: string
tls:
description: TLS indicate route trait will create SSL secret using
cert-manager with specified issuer If this is nil, route trait will
use a selfsigned issuer
properties:
issuerName:
type: string
type:
description: Type indicate the issuer is ClusterIssuer or NamespaceIssuer
type: string
type: object
workloadRef:
description: WorkloadReference to the workload whose metrics needs
to be exposed
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
required:
- host
type: object
status:
description: RouteStatus defines the observed state of Route
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
ingress:
description: A TypedReference refers to an object by Name, Kind, and
APIVersion. It is commonly used to reference cluster-scoped objects
or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
service:
description: A TypedReference refers to an object by Name, Kind, and
APIVersion. It is commonly used to reference cluster-scoped objects
or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,37 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: backend
annotations:
definition.oam.dev/apiVersion: "standard.oam.dev/v1alpha1"
definition.oam.dev/kind: "Containerized"
spec:
definitionRef:
name: containerizeds.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
extension:
template: |
#Template: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Containerized"
metadata:
name: backend.name
spec: {
replicas: 1
podSpec: {
containers: [{
image: backend.image
name: backend.name
}]
}
}
}
backend: {
name: string
// +usage=specify app image
// +short=i
image: string
}

View File

@@ -1,42 +0,0 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: backend-service
annotations:
definition.oam.dev/apiVersion: "core.oam.dev/v1alpha2"
definition.oam.dev/kind: "ContainerizedWorkload"
spec:
definitionRef:
name: containerizeds.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
- apiVersion: v1
kind: Service
extension:
template: |
#Template: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ContainerizedWorkload"
metadata: name: containerized.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
}
}
containerized: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

View File

@@ -689,7 +689,7 @@ spec:
serviceAccountName: cert-manager-cainjector
containers:
- name: cert-manager
image: "quay.io/jetstack/cert-manager-cainjector:v0.12.0"
image: "oamdev/cert-manager-cainjector:v0.12.0"
imagePullPolicy: IfNotPresent
args:
- --v=2
@@ -737,7 +737,7 @@ spec:
serviceAccountName: cert-manager
containers:
- name: cert-manager
image: "quay.io/jetstack/cert-manager-controller:v0.12.0"
image: "oamdev/cert-manager-controller:v0.12.0"
imagePullPolicy: IfNotPresent
args:
- --v=2
@@ -793,7 +793,7 @@ spec:
serviceAccountName: cert-manager-webhook
containers:
- name: cert-manager
image: "quay.io/jetstack/cert-manager-webhook:v0.12.0"
image: "oamdev/cert-manager-webhook:v0.12.0"
imagePullPolicy: IfNotPresent
args:
- --v=2

View File

@@ -3,8 +3,8 @@ kind: WorkloadDefinition
metadata:
name: containerizeds.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: "core.oam.dev/v1alpha2"
definition.oam.dev/kind: "ContainerizedWorkload"
definition.oam.dev/apiVersion: "standard.oam.dev/v1alpha1"
definition.oam.dev/kind: "Containerized"
spec:
definitionRef:
name: containerizeds.standard.oam.dev
@@ -16,23 +16,26 @@ spec:
extension:
template: |
#Template: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ContainerizedWorkload"
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Containerized"
metadata:
name: containerized.name
name: webservice.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
replicas: 1
podSpec: {
containers: [{
image: webservice.image
name: webservice.name
ports: [{
containerPort: webservice.port
protocol: "TCP"
name: "default"
}]
}]
}
}
}
containerized: {
webservice: {
name: string
// +usage=specify app image
// +short=i
@@ -40,4 +43,4 @@ spec:
// +usage=specify port for container
// +short=p
port: *6379 | int
}
}

View File

@@ -2,6 +2,10 @@ apiVersion: core.oam.dev/v1alpha2
kind: ScopeDefinition
metadata:
name: healthscopes.core.oam.dev
annotations:
definition.oam.dev/apiVersion: core.oam.dev/v1alpha2
definition.oam.dev/kind: HealthScope
namespace: default
spec:
workloadRefsPath: spec.workloadRefs
allowComponentOverlap: true

View File

@@ -5,6 +5,7 @@ metadata:
definition.oam.dev/apiVersion: core.oam.dev/v1alpha2
definition.oam.dev/kind: ManualScalerTrait
name: manualscalertraits.core.oam.dev
namespace: default
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload

View File

@@ -2,6 +2,10 @@ apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: metricstraits.standard.oam.dev
namespace: default
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: MetricsTrait
spec:
appliesToWorkloads:
- containerizedworkloads.core.oam.dev
@@ -10,32 +14,4 @@ spec:
- statefulsets.apps
definitionRef:
name: metricstraits.standard.oam.dev
workloadRefPath: spec.workloadRef
extension:
template: |
#Template: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "MetricsTrait"
metadata:
name: metricstraits.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
}
}
containerized: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}
workloadRefPath: spec.workloadRef

View File

@@ -0,0 +1,37 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: routes.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Route
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload
- standard.oam.dev/v1alpha1.Containerized
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: routes.standard.oam.dev
extension:
template: |
#Template: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Route"
spec: {
host: route.domain
path: route.path
tls: {
issuerName: route.issuer
}
backend: {
port: route.port
}
}
}
route: {
domain: *"" | string
path: *"" | string
port: *443 | int
issuer: *"" | string
}

View File

@@ -14,34 +14,34 @@ spec:
- logging
template: |
#Template: {
apiVersion: "v1"
kind: "Job"
metadata: name: task
spec: {
parallelism: taskSpec.count
completions: taskSpec.count
template:
spec:
containers: [{
image: taskSpec.image
name: taskSpec.name
ports: [{
containerPort: taskSpec.port
protocol: "TCP"
name: "default"
}]
}]
}
}
taskSpec: {
// +usage=specify number of tasks to run in parallel
// +short=c
count: *1 | int
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}
apiVersion: "v1"
kind: "Job"
metadata: name: task.name
spec: {
parallelism: task.count
completions: task.count
template:
spec:
containers: [{
image: task.image
name: task.name
ports: [{
containerPort: task.port
protocol: "TCP"
name: "default"
}]
}]
}
}
task: {
// +usage=specify number of tasks to run in parallel
// +short=c
count: *1 | int
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: vela-config
namespace: default
data:
certificates.cert-manager.io: |
{
"repo": "jetstack",
"urL": "https://charts.jetstack.io",
"name": "cert-manager",
"namespace": "cert-manager",
"version": "1.0.0"
}

View File

@@ -1,10 +1,10 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: web-service
name: webservice
annotations:
definition.oam.dev/apiVersion: "core.oam.dev/v1alpha2"
definition.oam.dev/kind: "ContainerizedWorkload"
definition.oam.dev/apiVersion: "standard.oam.dev/v1alpha1"
definition.oam.dev/kind: "Containerized"
spec:
definitionRef:
name: containerizeds.standard.oam.dev
@@ -16,23 +16,26 @@ spec:
extension:
template: |
#Template: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ContainerizedWorkload"
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Containerized"
metadata:
name: containerized.name
name: webservice.name
spec: {
containers: [{
image: containerized.image
name: containerized.name
ports: [{
containerPort: containerized.port
protocol: "TCP"
name: "default"
}]
}]
replicas: 1
podSpec: {
containers: [{
image: webservice.image
name: webservice.name
ports: [{
containerPort: webservice.port
protocol: "TCP"
name: "default"
}]
}]
}
}
}
containerized: {
webservice: {
name: string
// +usage=specify app image
// +short=i
@@ -40,4 +43,4 @@ spec:
// +usage=specify port for container
// +short=p
port: *6379 | int
}
}

View File

@@ -6,8 +6,8 @@ replicaCount: 1
useWebhook: true
image:
repository: oamdev/vela-core
tag: 0.1
pullPolicy: IfNotPresent
tag: latest
pullPolicy: Always
imagePullSecrets: []
nameOverride: ""

View File

@@ -6,8 +6,11 @@ import (
"os"
"strconv"
monitoring "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/crossplane/crossplane-runtime/pkg/logging"
velacore "github.com/oam-dev/kubevela/api/v1alpha1"
velacontroller "github.com/oam-dev/kubevela/pkg/controller"
"github.com/oam-dev/kubevela/pkg/controller/dependency"
velawebhook "github.com/oam-dev/kubevela/pkg/webhook"
oamcore "github.com/crossplane/oam-kubernetes-runtime/apis/core"
oamcontroller "github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
oamv1alpha2 "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2"
@@ -16,26 +19,31 @@ import (
injectorcontroller "github.com/oam-dev/trait-injector/controllers"
"github.com/oam-dev/trait-injector/pkg/injector"
"github.com/oam-dev/trait-injector/pkg/plugin"
monitoring "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/crossplane/crossplane-runtime/pkg/logging"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
velacore "github.com/oam-dev/kubevela/api/v1alpha1"
velacontroller "github.com/oam-dev/kubevela/pkg/controller"
velawebhook "github.com/oam-dev/kubevela/pkg/webhook"
)
var scheme = runtime.NewScheme()
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = crdv1.AddToScheme(scheme)
_ = oamcore.AddToScheme(scheme)
_ = monitoring.AddToScheme(scheme)
_ = velacore.AddToScheme(scheme)
_ = injectorv1alpha1.AddToScheme(scheme)
_ = certmanager.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
@@ -93,6 +101,17 @@ func main() {
os.Exit(1)
}
k8sClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{Scheme: scheme})
if err != nil {
setupLog.Error(err, "unable to create a kubernetes client")
os.Exit(1)
}
if err = dependency.Install(k8sClient); err != nil {
setupLog.Error(err, "unable to install the dependency")
os.Exit(1)
}
if useWebhook {
setupLog.Info("vela webhook enabled, will serving at :" + strconv.Itoa(webhookPort))
oamwebhook.Add(mgr)

View File

@@ -17,6 +17,7 @@ import (
"github.com/crossplane/oam-kubernetes-runtime/apis/core"
"github.com/gosuri/uitable"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
"github.com/spf13/cobra"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
@@ -34,7 +35,7 @@ var chartTGZSource string
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = certmanager.AddToScheme(scheme)
_ = core.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
@@ -97,6 +98,7 @@ func newCommand() *cobra.Command {
// Getting Start
NewVersionCommand(),
commands.NewInitCommand(commandArgs, ioStream),
// Apps
commands.NewAppsCommand(commandArgs, ioStream),
@@ -129,6 +131,7 @@ func newCommand() *cobra.Command {
fset := flag.NewFlagSet("logs", flag.ContinueOnError)
klog.InitFlags(fset)
_ = fset.Set("v", "-1")
return cmds
}

View File

@@ -74,3 +74,23 @@ rules:
- get
- patch
- update
- apiGroups:
- standard.oam.dev
resources:
- routes
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- standard.oam.dev
resources:
- routes/status
verbs:
- get
- patch
- update

View File

@@ -0,0 +1,19 @@
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: letsencrypt-prod
spec:
acme:
# You must replace this email address with your own.
# Let's Encrypt will use this to contact you about expiring
# certificates, and issues related to your account.
email: wonderflow@icloud.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# Secret resource that will be used to store the account's private key.
name: example-issuer-account-key
# Add a single challenge solver, HTTP01 using nginx
solvers:
- http01:
ingress:
class: nginx

View File

@@ -1,42 +1,37 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: ingresses.networking.k8s.io
name: routes.standard.oam.dev
annotations:
"definition.oam.dev/apiVersion": "networking.k8s.io/v1beta1"
"definition.oam.dev/kind": "Ingress"
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Route
spec:
revisionEnabled: true
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload
- standard.oam.dev/v1alpha1.Containerized
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: ingresses.networking.k8s.io
name: routes.standard.oam.dev
extension:
install:
helm:
repo: stable
name: nginx-ingress
url: https://kubernetes-charts.storage.googleapis.com/
version: 1.41.2
template: |
#Template: {
apiVersion: "networking.k8s.io/v1beta1"
kind: "Ingress"
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Route"
spec: {
rules: [{
host: route.domain
http: paths: [{
backend: {
serviceName: route.service
servicePort: route.port
}}]
}]
host: route.domain
path: route.path
tls: {
issuerName: route.issuer
}
backend: {
port: route.port
}
}
}
route: {
domain: string
port: *80 | int
service: string
domain: *"" | string
path: *"" | string
port: *443 | int
issuer: *"" | string
}

View File

@@ -12,9 +12,7 @@ export default defineConfig({
hmr: true,
},
locale: {
// default zh-CN
default: 'en-US',
// default true, when it is true, will use `navigator.language` overwrite default
antd: false,
baseNavigator: false,
},
@@ -36,13 +34,11 @@ export default defineConfig({
routes: [
{
path: '/',
// redirect: `/${envname}/ApplicationList`,
redirect: `/ApplicationList`,
},
{
name: 'ApplicationList',
icon: 'table',
// path: `/${envname}/ApplicationList`,
path: `/ApplicationList`,
component: './ApplicationList',
},
@@ -72,30 +68,23 @@ export default defineConfig({
path: '/ApplicationList/CreateApplication',
component: './ApplicationList/CreateApplication',
},
{
name: 'ApplicationList.Components',
hideInMenu: true,
path: '/ApplicationList/:appName/Components',
component: './ApplicationList/Components',
},
{
name: 'ApplicationList.Components.createComponent',
hideInMenu: true,
path: '/ApplicationList/:appName/createComponent',
component: './ApplicationList/CreateComponent',
},
{
name: 'Workload',
icon: 'table',
path: '/Workload',
routes: [
// {
// name: 'Deployment',
// icon: 'table',
// path: '/Workload/Deployment',
// component: './Workload/Deployment',
// },
// {
// name: 'Containerized',
// icon: 'smile',
// path: '/Workload/Containerized',
// component: './Workload/Containerized',
// },
// {
// name: 'Detail',
// icon: 'smile',
// path: '/Workload/Detail',
// component: './Workload/Detail',
// hideInMenu: true,
// },
{
name: 'WorkloadItem',
icon: 'smile',
@@ -109,25 +98,6 @@ export default defineConfig({
name: 'Traits',
icon: 'table',
routes: [
// {
// name: 'Scale',
// icon: 'table',
// path: '/Traits/Scale',
// component: './Traits/Scale',
// },
// {
// name: 'Rollout',
// icon: 'smile',
// path: '/Traits/Rollout',
// component: './Traits/Rollout',
// },
// {
// name: 'Detail',
// icon: 'smile',
// path: '/Traits/Detail',
// component: './Traits/Detail',
// hideInMenu: true,
// },
{
name: 'TraitItem',
icon: 'smile',
@@ -136,12 +106,6 @@ export default defineConfig({
},
],
},
// {
// name: 'Release',
// icon: 'table',
// path: '/Release',
// component: './Release',
// },
{
name: 'Capability',
icon: 'table',
@@ -183,8 +147,13 @@ export default defineConfig({
],
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
theme: {
// ...darkTheme,
// 主题配置
'primary-color': defaultSettings.primaryColor,
'link-color': defaultSettings.linkColor,
'link-hover-color': defaultSettings.linkHoverColor,
'disabled-bg': defaultSettings.disabledBg,
'disabled-color': defaultSettings.disabledColor,
'btn-disable-color': defaultSettings.btnDisableColor,
},
// @ts-ignore
title: false,

View File

@@ -1,14 +1,19 @@
const proSettings = {
navTheme: 'dark',
// 拂晓蓝
primaryColor: '#1890ff',
// 主题颜色配置
primaryColor: '#1B58F4', // 全局主色
linkColor: '#1B58F4', // 链接色
linkHoverColor: '#1B58F4',
disabledBg: '#EBEBEB', // 失效背景色,
disabledColor: '#BEBEBE', // 失效文本色,
btnDisableColor: '#A4A4A4', // 禁用btn文字颜色
layout: 'side',
contentWidth: 'Fluid',
fixedHeader: false,
fixSiderbar: true,
colorWeak: false,
menu: {
locale: true,
locale: false,
},
title: 'Micro App Engine',
pwa: false,

View File

@@ -8,11 +8,8 @@
export default {
dev: {
'/api': {
target: 'http://123.56.222.218:8081/',
target: 'http://30.11.171.29:38081/',
changeOrigin: true,
// pathRewrite: {
// "^/api": "",
// },
},
},
test: {

View File

@@ -35,34 +35,40 @@ export default class CreateTraitItem extends React.PureComponent {
return this.formRefStep2.current.getFieldsValue();
};
validateFields = () => {
return this.formRefStep2.current.validateFields();
};
setDefaultValue = (traitType) => {
this.formRefStep2.current.setFieldsValue({ name: traitType });
this.traitSelectChange(traitType);
};
traitSelectChange = async (value, isType = 1) => {
const res = await this.props.dispatch({
type: 'trait/getTraitByName',
payload: {
traitName: value,
},
});
this.setState({
parameters: res.parameters,
});
if (isType === 2) {
this.formRefStep2.current.setFieldsValue(this.props.initialValues);
} else if (isType) {
// 进行默认值填写
const parameters = _.get(res, 'parameters', []);
if (parameters.length) {
const initialObj = {};
parameters.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep2.current.setFieldsValue(initialObj);
if (value) {
const res = await this.props.dispatch({
type: 'trait/getTraitByName',
payload: {
traitName: value,
},
});
this.setState({
parameters: res.parameters,
});
if (isType === 2) {
this.formRefStep2.current.setFieldsValue(this.props.initialValues);
} else if (isType) {
// 进行默认值填写
const parameters = _.get(res, 'parameters', []);
if (parameters.length) {
const initialObj = {};
parameters.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep2.current.setFieldsValue(initialObj);
}
}
}
};
@@ -77,8 +83,12 @@ export default class CreateTraitItem extends React.PureComponent {
name="control-ref"
className="traitItem"
>
<Form.Item name="name" label="Trait">
<Select placeholder="Select a Trait" allowClear onChange={this.traitSelectChange}>
<Form.Item
name="name"
label="Trait"
rules={[{ required: true, message: 'Please Select a Trait!' }]}
>
<Select placeholder="Select a Trait" onChange={this.traitSelectChange}>
{availableTraitList.map((item) => {
return (
<Option value={item.name} key={item.name}>
@@ -92,8 +102,34 @@ export default class CreateTraitItem extends React.PureComponent {
<div className="relativeBox">
{this.state.parameters ? (
this.state.parameters.map((item) => {
return (
<Form.Item name={item.name} label={item.name} key={item.name}>
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[0-9]*$/, message: `${item.name} only use digits(0-9).` },
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
</Form.Item>
);

View File

@@ -23,9 +23,8 @@ export default class WorkSpaceDropDown extends React.Component {
return env.current === '*';
});
this.setState({
envs: envs,
workSpaceName: envName,
namespace: namespace,
namespace,
});
this.props.dispatch({
type: 'globalData/currentEnv',

View File

@@ -1,18 +1,30 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Row, Col, Modal, Select, message } from 'antd';
import { Button, Row, Col, Modal, Select, message, Breadcrumb, Form, Input } from 'antd';
import './index.less';
import { connect } from 'dva';
import { Link } from 'umi';
import _ from 'lodash';
const { Option } = Select;
const layout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
@connect(({ loading, applist, globalData }) => ({
loadingAll: loading.models.applist,
currentEnv: globalData.currentEnv,
returnObj: applist.returnObj,
}))
class Trait extends React.Component {
formRefStep2 = React.createRef();
constructor(props) {
super(props);
this.state = {
@@ -37,28 +49,61 @@ class Trait extends React.Component {
};
showModal = () => {
this.setState({
visible: true,
});
this.setState(
{
visible: true,
},
() => {
if (this.formRefStep2.current) {
this.formRefStep2.current.resetFields();
}
},
);
};
handleOk = () => {
const { selectValue } = this.state;
if (selectValue) {
this.setState({
visible: false,
handleOk = async () => {
await this.formRefStep2.current.validateFields();
const { title } = this.props.propsObj;
if (title) {
const submitObj = {
name: title,
flags: [],
};
const submitData = this.formRefStep2.current.getFieldValue();
Object.keys(submitData).forEach((currentKey) => {
if (currentKey !== 'name' && currentKey !== 'appName' && submitData[currentKey]) {
submitObj.flags.push({
name: currentKey,
value: submitData[currentKey].toString(),
});
}
});
const { history } = this.props.propsObj;
history.push({
pathname: '/ApplicationList/ApplicationListDetail',
state: {
appName: selectValue,
envName: this.props.currentEnv,
traitType: this.props.propsObj.title,
},
});
} else {
message.warn('please select a application');
const { currentEnv: envName } = this.props;
const { appName } = submitData;
if (envName && appName) {
const res = await this.props.dispatch({
type: 'trait/attachOneTraits',
payload: {
envName,
appName,
params: submitObj,
},
});
if (res) {
this.setState({
visible: false,
});
message.success(res);
const { history } = this.props.propsObj;
history.push({
pathname: '/ApplicationList/ApplicationListDetail',
state: {
appName,
envName,
},
});
}
}
}
};
@@ -77,98 +122,175 @@ class Trait extends React.Component {
onSearch = () => {};
render() {
const { btnValue, title, settings, btnIsShow, crdInfo, appliesTo } = this.props.propsObj;
const { btnValue, title, settings = [], btnIsShow, crdInfo, appliesTo } = this.props.propsObj;
const initialObj = {};
if (settings.length) {
settings.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
}
const appList = _.get(this.props, 'returnObj', []);
return (
<PageContainer>
<Row>
<Col span="11">
<div className="deployment">
<Row>
<Col span="22">
<p className="title">{title}</p>
{crdInfo ? (
<p>
{crdInfo.apiVersion}
<span>,kind=</span>
{crdInfo.kind}
</p>
) : (
<p />
)}
</Col>
</Row>
<Row>
<Col span="22">
<p className="title">Applies To</p>
<p>{Array.isArray(appliesTo) ? appliesTo.join(', ') : appliesTo}</p>
</Col>
</Row>
<p className="title">Configurable Properties:</p>
{settings.map((item, index) => {
return (
<Row key={index.toString()}>
<Col span="8">
<p>{item.name}</p>
</Col>
<Col span="16">
<p>{item.default || item.usage}</p>
</Col>
</Row>
);
})}
</div>
<Button
type="primary"
className="create-button"
onClick={this.showModal}
style={{ display: btnIsShow ? 'block' : 'none' }}
>
{btnValue}
</Button>
<Modal
title="Select a Application"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Next
</Button>,
]}
>
<Select
showSearch
allowClear
value={this.state.selectValue}
style={{ width: '100%' }}
placeholder="Select a Application"
optionFilterProp="children"
onChange={this.onChange}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Traits</Breadcrumb.Item>
<Breadcrumb.Item>{title}</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Row>
<Col span="11">
<div className="deployment">
<Row>
<Col span="22">
<p className="title">{title}</p>
{crdInfo ? (
<p>
{crdInfo.apiVersion}
<span>,kind=</span>
{crdInfo.kind}
</p>
) : (
<p />
)}
</Col>
</Row>
<Row>
<Col span="22">
<p className="title">Applies To:</p>
<p>{Array.isArray(appliesTo) ? appliesTo.join(', ') : appliesTo}</p>
</Col>
</Row>
<p className="title">Configurable Properties:</p>
{settings.map((item, index) => {
return (
<Row key={index.toString()}>
<Col span="8">
<p>{item.name}</p>
</Col>
<Col span="16">
<p>{item.default || item.usage}</p>
</Col>
</Row>
);
})}
</div>
<Button
type="primary"
className="create-button"
onClick={this.showModal}
style={{ display: btnIsShow ? 'block' : 'none' }}
>
{appList.length ? (
appList.map((item) => {
return (
<Option key={item.name} value={item.name}>
{item.name}
</Option>
);
})
) : (
<Fragment />
)}
</Select>
</Modal>
</Col>
</Row>
</PageContainer>
{btnValue}
</Button>
<Modal
title="Attach Trait"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Submit
</Button>,
]}
>
<Form
labelAlign="left"
{...layout}
ref={this.formRefStep2}
name="control-ref"
className="traitItem"
initialValues={initialObj}
>
<Form.Item
label="Target"
name="appName"
rules={[{ required: true, message: 'Please Select a Application!' }]}
>
<Select
showSearch
allowClear
value={this.state.selectValue}
style={{ width: '100%' }}
placeholder="Select a Application"
optionFilterProp="children"
onChange={this.onChange}
onSearch={this.onSearch}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{appList.length ? (
appList.map((item) => {
return (
<Option key={item.name} value={item.name}>
{item.name}
</Option>
);
})
) : (
<Fragment />
)}
</Select>
</Form.Item>
<div className="relativeBox">
<Form.Item label="Properties" />
{settings ? (
settings.map((item) => {
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{
pattern: /^[0-9]*$/,
message: `${item.name} only use digits(0-9).`,
},
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
</Form.Item>
);
})
) : (
<></>
)}
</div>
</Form>
</Modal>
</Col>
</Row>
</PageContainer>
</div>
);
}
}

View File

@@ -1,69 +1,70 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Row, Col } from 'antd';
import { Button, Row, Col, Breadcrumb } from 'antd';
import { Link } from 'umi';
import './index.less';
export default class Workload extends React.PureComponent {
render() {
const {
btnValue,
pathname,
title,
crdInfo,
state,
settings,
hrefAddress,
btnIsShow,
} = this.props.propsObj;
const { btnValue, pathname, title, crdInfo, state, settings, btnIsShow } = this.props.propsObj;
return (
<PageContainer>
<Row>
<Col span="11">
<div className="deployment">
<a href={hrefAddress}>?</a>
<Row>
<Col span="22">
<p className="title">{title}</p>
{crdInfo ? (
<p>
{crdInfo.apiVersion}
<span>,kind=</span>
{crdInfo.kind}
</p>
) : (
<p />
)}
</Col>
</Row>
<p className="title">Configurable Settings:</p>
{settings.map((item, index) => {
if (item.name === 'name') {
return <Fragment key={index.toString()} />;
}
return (
<Row key={index.toString()}>
<Col span="8">
<p>{item.name}</p>
</Col>
<Col span="16">
{
// eslint-disable-next-line consistent-return
}
<p>{item.default || item.usage}</p>
</Col>
</Row>
);
})}
</div>
<Link to={{ pathname, state }} style={{ display: btnIsShow ? 'block' : 'none' }}>
<Button type="primary" className="create-button">
{btnValue}
</Button>
</Link>
</Col>
</Row>
</PageContainer>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Workloads</Breadcrumb.Item>
<Breadcrumb.Item>{title}</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Row>
<Col span="11">
<div className="deployment">
<Row>
<Col span="22">
<p className="title">{title}</p>
{crdInfo ? (
<p>
{crdInfo.apiVersion}
<span>,kind=</span>
{crdInfo.kind}
</p>
) : (
<p />
)}
</Col>
</Row>
<p className="title">Configurable Settings:</p>
{settings.map((item, index) => {
if (item.name === 'name') {
return <Fragment key={index.toString()} />;
}
return (
<Row key={index.toString()}>
<Col span="8">
<p>{item.name}</p>
</Col>
<Col span="16">
{
// eslint-disable-next-line consistent-return
}
<p>{item.default || item.usage}</p>
</Col>
</Row>
);
})}
</div>
<Link to={{ pathname, state }} style={{ display: btnIsShow ? 'block' : 'none' }}>
<Button type="primary" className="create-button">
{btnValue}
</Button>
</Link>
</Col>
</Row>
</PageContainer>
</div>
);
}
}

View File

@@ -29,13 +29,6 @@ beforeEach(async () => {
describe('Ant Design Pro E2E test', () => {
const testPage = (path) => async () => {
await page.goto(`${BASE_URL}${path}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
};
const routers = formatter(RouterConfig);
@@ -46,12 +39,5 @@ describe('Ant Design Pro E2E test', () => {
it('topmenu should have footer', async () => {
const params = '?navTheme=light&layout=topmenu';
await page.goto(`${BASE_URL}${params}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
});
});

View File

@@ -69,3 +69,19 @@ ol {
.ant-page-header-heading {
display: none !important;
}
.ant-pro-page-container-warp {
display: none;
}
.ant-pro-basicLayout-content {
margin: 0 !important;
}
.ant-pro-basicLayout-content .ant-pro-page-container {
margin: 0 !important;
}
.breadCrumb {
padding: 12px 24px;
background: #fff;
}
.ant-breadcrumb a:hover {
color: #1b58f4 !important;
}

View File

@@ -4,7 +4,7 @@
* https://github.com/ant-design/ant-design-pro-layout
*/
import ProLayout from '@ant-design/pro-layout';
import React, { useEffect } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { Link, useIntl, connect, history } from 'umi';
import RightContent from '@/components/GlobalHeader/RightContent';
import {
@@ -16,15 +16,6 @@ import {
} from '@ant-design/icons';
import _ from 'lodash';
// const menuDataRender = (menuList) => {
// return menuList.map((item) => {
// const localItem = {
// ...item,
// children: item.children ? menuDataRender(item.children) : undefined,
// };
// return localItem;
// });
// };
const AddIcon = (menuData) => {
return menuData.map((item) => {
const name = _.get(item, 'name', '');
@@ -52,18 +43,43 @@ const AddIcon = (menuData) => {
const BasicLayout = (props) => {
const { settings, dispatch, menus } = props;
const [currentSelectKeys, setCurrentSelectedKeys] = useState('');
const timerRef = useRef();
const getCurrentSelectKeys = () => {
const pathnameCur = props.history.location.pathname;
if (pathnameCur) {
if (pathnameCur.includes('Application')) {
setCurrentSelectedKeys(['applist']);
} else if (pathnameCur.includes('Capability')) {
setCurrentSelectedKeys(['Capability']);
} else if (pathnameCur.includes('System/Env')) {
setCurrentSelectedKeys(['Env']);
} else if (pathnameCur.includes('Workload')) {
const arr = pathnameCur.split('/');
const key = arr[arr.length - 1];
setCurrentSelectedKeys([key]);
} else if (pathnameCur.includes('Traits')) {
const arr = pathnameCur.split('/');
const key = arr[arr.length - 1];
setCurrentSelectedKeys([key]);
}
}
};
useEffect(() => {
if (dispatch) {
// dispatch({
// type: 'user/fetchCurrent',
// });
// dispatch({
// type: 'settings/getSetting',
// });
dispatch({
type: 'menus/getMenuData',
});
}
timerRef.current = props.history.listen((route) => {
getCurrentSelectKeys(route.pathname);
});
return () => {
if (timerRef.current) {
timerRef.current = null;
}
};
// setCurrentSelectedKeys('applist')
}, []);
const { formatMessage } = useIntl();
@@ -75,9 +91,19 @@ const BasicLayout = (props) => {
if (menuItemProps.isUrl || !menuItemProps.path) {
return defaultDom;
}
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
// return <Link to={menuItemProps.path}>{defaultDom}</Link>;
return (
<div
onClick={() => {
setCurrentSelectedKeys([menuItemProps.key]);
history.push(menuItemProps.path);
}}
>
{defaultDom}
</div>
);
}}
selectedKeys={currentSelectKeys}
breadcrumbRender={(routers = []) => [
{
path: '/',

View File

@@ -2,17 +2,15 @@ import { getapplist, createApp, getAppDetail, deleteApp } from '@/services/appli
const TestModel = {
namespace: 'applist',
state: {
// initailState: '8880'
},
state: {},
effects: {
*getList({ payload }, { call, put }) {
const res = yield call(getapplist, payload);
// getlist是引入services层那个js文件的getlist方法payload是后台要求传递的参数res就是后台返过来的数据
yield put({
type: 'addList', // 这就是reducer的addNum方法put用来触发reducer中的方法payload是传过去的参数。同时也能触发同等级effects中的方法
type: 'addList',
payload: {
returnObj: res, // 把后台返回的数据赋值给num,假如哪个reducer中的方法是由这里effects去触发的哪个num名必须是这里的名字num如果reducer中的方法不是这触发那名字可以随意取
returnObj: res,
},
});
},

View File

@@ -10,9 +10,7 @@ import {
const TestModel = {
namespace: 'capability',
state: {
// initailState: '8880'
},
state: {},
effects: {
*getCapabilityCenterlist({ payload }, { call }) {
const res = yield call(getCapabilityCenterlist, payload);

View File

@@ -6,7 +6,7 @@ const globalModel = {
effects: {
*currentEnv({ payload }, { put }) {
yield put({
type: 'setCurrentEnv', // 这就是reducer的addNum方法put用来触发reducer中的方法payload是传过去的参数。同时也能触发同等级effects中的方法
type: 'setCurrentEnv',
payload,
});
},

View File

@@ -1,36 +1,31 @@
import { capabilityList } from '@/services/capability.js';
import { getTraits } from '@/services/trait.js';
import { getWorkload } from '@/services/workload.js';
function getMenuList(response) {
function getMenuList(workload, trait) {
let workloadList = [];
let traitList = [];
// eslint-disable-next-line no-param-reassign
response = response.filter((item) => {
return item.status === 'installed';
});
response.forEach((item) => {
if (item.type === 'workload') {
workloadList.push(item.name);
} else if (item.type === 'trait') {
traitList.push(item.name);
}
});
// 在此之前要对workloadList和traitList进行一次去重操作
workloadList = workloadList.map((item) => {
// eslint-disable-next-line no-param-reassign
item = item.charAt(0).toUpperCase() + item.slice(1);
return {
name: item,
path: `/Workload/${item}`,
};
});
traitList = traitList.map((item) => {
// eslint-disable-next-line no-param-reassign
item = item.charAt(0).toUpperCase() + item.slice(1);
return {
name: item,
path: `/Traits/${item}`,
};
});
if (workload) {
workloadList = workload.map((item) => {
let name1 = item.name;
name1 = name1.charAt(0).toUpperCase() + name1.slice(1);
return {
name: name1,
path: `/Workload/${name1}`,
key: name1,
};
});
}
if (trait) {
traitList = trait.map((item) => {
let name1 = item.name;
name1 = name1.charAt(0).toUpperCase() + name1.slice(1);
return {
name: name1,
path: `/Traits/${name1}`,
key: name1,
};
});
}
// 只是动态生成侧边栏(name,path,icon)路由还是config.js里面配置的路由
const menuList = [
{
@@ -41,6 +36,7 @@ function getMenuList(response) {
name: 'ApplicationList',
icon: 'Table',
path: `/ApplicationList`,
key: 'applist',
},
{
name: 'ApplicationList.ApplicationListDetail',
@@ -76,13 +72,10 @@ function getMenuList(response) {
},
],
},
// {
// name: 'Release',
// path: '/Release',
// },
{
name: 'Capability',
path: '/Capability',
key: 'Capability',
},
{
path: '/System',
@@ -91,6 +84,7 @@ function getMenuList(response) {
{
name: 'Env',
path: '/System/Env',
key: 'Env',
},
],
},
@@ -110,8 +104,9 @@ const TestModel = {
},
effects: {
*getMenuData({ payload }, { call, put }) {
let response = yield call(capabilityList, payload);
response = getMenuList(response);
const workloadList = yield call(getWorkload, payload);
const traitList = yield call(getTraits, payload);
const response = getMenuList(workloadList, traitList);
yield put({
type: 'saveMenuData',
payload: response,

View File

@@ -2,9 +2,7 @@ import { getTraitByName, getTraits, attachOneTraits, deleteOneTrait } from '@/se
const TestModel = {
namespace: 'trait',
state: {
// initailState: '8880'
},
state: {},
effects: {
*getTraitByName({ payload }, { call }) {
const res = yield call(getTraitByName, payload);

View File

@@ -2,9 +2,7 @@ import { createWorkload, getWorkload, getWorkloadByName } from '@/services/workl
const TestModel = {
namespace: 'workload',
state: {
// initailState: '8880'
},
state: {},
effects: {
*createWorkload({ payload }, { call }) {
const res = yield call(createWorkload, payload);

View File

@@ -43,8 +43,6 @@ const Topology = () => {
],
};
// const width = document.getElementById('container').scrollWidth;
// const height = document.getElementById('container').scrollHeight || 500;
const width = 1000;
const height = 400;

View File

@@ -1,11 +1,22 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Button, Row, Col, Tabs, Popconfirm, message, Tooltip, Modal, Spin } from 'antd';
import {
Button,
Row,
Col,
Tabs,
Popconfirm,
message,
Tooltip,
Modal,
Spin,
Breadcrumb,
} from 'antd';
import { connect } from 'dva';
import _ from 'lodash';
import { Link } from 'umi';
import CreateTraitItem from '../../../components/AttachOneTrait/index.jsx';
import Topology from './Topology.jsx';
const { TabPane } = Tabs;
@@ -27,18 +38,26 @@ class TableList extends React.Component {
}
componentDidMount() {
this.getInitialData(1);
this.getInitialData();
}
getInitialData = async (times) => {
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
const traitType = _.get(this.props, 'location.state.traitType', '');
getInitialData = async () => {
let appName = '';
let envName = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
appName,
envName,
});
if (appName && envName) {
this.setState({
envName,
appName,
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
@@ -54,23 +73,17 @@ class TableList extends React.Component {
const traits = await this.props.dispatch({
type: 'trait/getTraits',
});
this.setState({
traitList: traits,
});
if (traits) {
this.setState({
traitList: traits,
});
}
const workloadType = _.get(res, 'Workload.workload.kind', '');
if (workloadType && workloadType === 'ContainerizedWorkload') {
this.getAcceptTrait('containerized');
} else if (workloadType && workloadType === 'Deployment') {
this.getAcceptTrait('deployment');
}
// 如果traitType存在是从特定trait跳转来新增单个trait的
if (traitType && times === 1) {
// this.createTrait(traitType)
await this.setState({
visible: true,
});
this.child.setDefaultValue(traitType);
}
}
};
@@ -88,7 +101,7 @@ class TableList extends React.Component {
deleteApp = async (e) => {
e.stopPropagation();
const { currentEnv: envName } = this.props;
const { envName } = this.state;
const { appDetailData } = this.state;
const appName = _.get(appDetailData, 'Workload.workload.metadata.name', '');
if (appName && envName) {
@@ -138,6 +151,7 @@ class TableList extends React.Component {
};
handleOk = async () => {
await this.child.validateFields();
const submitData = this.child.getSelectValue();
if (submitData.name) {
const submitObj = {
@@ -187,26 +201,10 @@ class TableList extends React.Component {
gotoWorkloadDetail = (e) => {
e.stopPropagation();
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
if (appName && envName) {
this.props.history.push({
pathname: '/ApplicationList/WorkloadDetail',
state: { appName, envName },
});
}
};
gotoTraitDetail = (e, traitItem) => {
gotoTraitDetail = (e) => {
e.stopPropagation();
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
if (appName && envName) {
this.props.history.push({
pathname: '/ApplicationList/TraitDetail',
state: { traitItem, appName, envName },
});
}
};
render() {
@@ -214,277 +212,277 @@ class TableList extends React.Component {
const Workload = _.get(this.state.appDetailData, 'Workload.workload', {});
const Traits = _.get(this.state.appDetailData, 'Traits', []);
let containers = {};
// if (Workload.kind === 'ContainerizedWorkload') {
// containers = _.get(Workload, 'spec.containers[0]', {});
// } else if (Workload.kind === 'Deployment') {
// containers = _.get(Workload, 'spec.template.spec.containers[0]', {});
// }
containers = _.get(Workload, 'spec.containers[0]', {});
let { loadingAll } = this.props;
loadingAll = loadingAll || false;
const colorObj = {
Deployed: '#4CAF51',
Staging: '#F44337',
UNKNOWN: '#1890ff',
};
return (
<PageContainer>
<Spin spinning={loadingAll}>
<div className="card-container app-detial">
<h2>{_.get(Workload, 'metadata.name')}</h2>
<p style={{ marginBottom: '20px' }}>
{Workload.apiVersion}, Kind={Workload.kind}
</p>
<Tabs>
<TabPane tab="Summary" key="1">
<Row>
<Col span="11">
<div className="summaryBox1" onClick={(e) => this.gotoWorkloadDetail(e)}>
{/* <div className="summaryBox1"> */}
<Row>
<Col span="22">
<p className="title">{Workload.kind}</p>
<p>{Workload.apiVersion}</p>
</Col>
<Col span="2">
{/* <a href="JavaScript:;">?</a> */}
<p className="title hasCursor" onClick={this.hrefClick}>
?
</p>
</Col>
</Row>
<p className="title">
Name:<span>{_.get(Workload, 'metadata.name')}</span>
</p>
<p className="title">Settings:</p>
<Row>
{Object.keys(containers).map((currentKey) => {
if (currentKey === 'ports') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>{_.get(containers[currentKey], '[0].containerPort', '')}</p>
</Col>
</Fragment>
);
// eslint-disable-next-line no-else-return
} else if (currentKey === 'name') {
return <Fragment key={currentKey} />;
}
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{containers[currentKey]}</p>
</Col>
</Fragment>
);
})}
</Row>
</div>
<div className="summaryBox2">
<p className="title">Status:</p>
<p>{status}</p>
{/* <Row>
<Col span="8">
<p>Available Replicas</p>
<p>Ready Replicas</p>
</Col>
<Col span="16">
<p>1</p>
<p>1</p>
</Col>
</Row> */}
</div>
<Popconfirm
title="Are you sure delete this app?"
onConfirm={(e) => this.deleteApp(e)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
<Button danger>Delete</Button>
</Popconfirm>
</Col>
<Col span="1" />
<Col span="10">
{Traits.length ? (
Traits.map((item, index) => {
const traitItem = _.get(item, 'trait', {});
const annotations = _.get(traitItem, 'metadata.annotations', {});
let traitType = 1;
const spec = _.get(traitItem, 'spec', {});
if (traitItem.kind === 'Ingress') {
traitType = 2;
}
return (
<div
className="summaryBox"
onClick={(e) => this.gotoTraitDetail(e, traitItem)}
key={index.toString()}
>
<Row>
<Col span="22">
<p className="title">{traitItem.kind}</p>
<p>{traitItem.apiVersion}</p>
</Col>
<Col span="2">
<p
className="title hasCursor"
onClick={(e) => {
e.stopPropagation();
}}
>
?
</p>
</Col>
</Row>
<Row>
{Object.keys(annotations).map((currentKey3) => {
return (
<Fragment key={currentKey3}>
<Col span="8">
<p>{currentKey3}:</p>
</Col>
<Col span="8">
<p>{annotations[currentKey3]}</p>
</Col>
</Fragment>
);
})}
</Row>
<p className="title">Properties:</p>
<Row>
{traitType === 2 ? (
<Fragment>
<Col span="8">
<p>domain</p>
</Col>
<Col span="16">
<p>{_.get(spec, 'rules[0].host', '')}</p>
</Col>
<Col span="8">
<p>service</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.serviceName',
'',
)}
</p>
</Col>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>ApplicationListDetail</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Spin spinning={loadingAll}>
<div className="card-container app-detial">
<h2>{_.get(Workload, 'metadata.name')}</h2>
<p style={{ marginBottom: '20px' }}>
{Workload.apiVersion}, Kind={Workload.kind}
</p>
<Tabs>
<TabPane tab="Summary" key="1">
<Row>
<Col span="11">
<div
className="summaryBox1"
onClick={(e) => this.gotoWorkloadDetail(e)}
style={{ background: colorObj[status] || '#1890ff' }}
>
<Row>
<Col span="22">
<p className="title">{Workload.kind}</p>
<p>{Workload.apiVersion}</p>
</Col>
<Col span="2">
<p className="title hasCursor" onClick={this.hrefClick}>
?
</p>
</Col>
</Row>
<p className="title">
Name:<span>{_.get(Workload, 'metadata.name')}</span>
</p>
<p className="title">Settings:</p>
<Row>
{Object.keys(containers).map((currentKey) => {
if (currentKey === 'ports') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.servicePort',
'',
)}
</p>
<p>{_.get(containers[currentKey], '[0].containerPort', '')}</p>
</Col>
</Fragment>
) : (
Object.keys(spec).map((currentKey) => {
);
// eslint-disable-next-line no-else-return
} else if (currentKey === 'name') {
return <Fragment key={currentKey} />;
// eslint-disable-next-line no-else-return
} else if (currentKey === 'env') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>env</p>
</Col>
<Col span="16">
<p>{_.get(containers[currentKey], '[0].value', '')}</p>
</Col>
</Fragment>
);
}
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{containers[currentKey]}</p>
</Col>
</Fragment>
);
})}
</Row>
</div>
<Popconfirm
title="Are you sure delete this app?"
onConfirm={(e) => this.deleteApp(e)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
<Button danger>Delete</Button>
</Popconfirm>
</Col>
<Col span="1" />
<Col span="10">
{Traits.length ? (
Traits.map((item, index) => {
const traitItem = _.get(item, 'trait', {});
const annotations = _.get(traitItem, 'metadata.annotations', {});
let traitType = 1;
const spec = _.get(traitItem, 'spec', {});
if (traitItem.kind === 'Ingress') {
traitType = 2;
}
return (
<div
className="summaryBox"
onClick={(e) => this.gotoTraitDetail(e, traitItem)}
key={index.toString()}
>
<Row>
<Col span="22">
<p className="title">{traitItem.kind}</p>
<p>{traitItem.apiVersion}</p>
</Col>
<Col span="2">
<p
className="title hasCursor"
onClick={(e) => {
e.stopPropagation();
}}
>
?
</p>
</Col>
</Row>
<Row>
{Object.keys(annotations).map((currentKey3) => {
return (
<Fragment key={currentKey}>
<Fragment key={currentKey3}>
<Col span="8">
<p>{currentKey}</p>
<p>{currentKey3}:</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
<Col span="8">
<p>{annotations[currentKey3]}</p>
</Col>
</Fragment>
);
})
)}
{/* {Object.keys(spec).map((currentKey) => {
return (
<Fragment key={currentKey}>
})}
</Row>
<p className="title">Properties:</p>
<Row>
{traitType === 2 ? (
<Fragment>
<Col span="8">
<p>{currentKey}</p>
<p>domain</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
<p>{_.get(spec, 'rules[0].host', '')}</p>
</Col>
<Col span="8">
<p>service</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.serviceName',
'',
)}
</p>
</Col>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.servicePort',
'',
)}
</p>
</Col>
</Fragment>
);
})} */}
</Row>
<div style={{ clear: 'both', height: '32px' }}>
<Popconfirm
title="Are you sure delete this trait?"
onConfirm={(e) => this.deleteTrait(e, item)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
<Button
danger
className="floatRight"
onClick={(e) => {
e.stopPropagation();
}}
) : (
Object.keys(spec).map((currentKey) => {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
</Col>
</Fragment>
);
})
)}
</Row>
<div style={{ clear: 'both', height: '32px' }}>
<Popconfirm
title="Are you sure delete this trait?"
onConfirm={(e) => this.deleteTrait(e, item)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
Delete
</Button>
</Popconfirm>
<Button
danger
className="floatRight"
onClick={(e) => {
e.stopPropagation();
}}
>
Delete
</Button>
</Popconfirm>
</div>
</div>
</div>
);
})
) : (
<Fragment />
)}
<Tooltip placement="top" title="Attach Trait">
<p
className="hasCursor"
style={{
fontSize: '30px',
display: 'inline-flex',
}}
onClick={this.createTrait}
>
+
</p>
</Tooltip>
</Col>
</Row>
</TabPane>
<TabPane tab="Topology" key="2">
{/* <p>Topology</p> */}
<Topology />
</TabPane>
</Tabs>
</div>
<Modal
title="attach a trait"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Confirm
</Button>,
]}
>
<CreateTraitItem
onRef={(ref) => {
this.child = ref;
}}
availableTraitList={this.state.availableTraitList}
initialValues={{}}
/>
</Modal>
</Spin>
</PageContainer>
);
})
) : (
<Fragment />
)}
<Tooltip placement="top" title="Attach Trait">
<p
className="hasCursor"
style={{
fontSize: '30px',
display: 'inline-flex',
}}
onClick={this.createTrait}
>
+
</p>
</Tooltip>
</Col>
</Row>
</TabPane>
<TabPane tab="Topology" key="2">
<p>Topology</p>
</TabPane>
</Tabs>
</div>
<Modal
title="Attach a Trait"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Confirm
</Button>,
]}
>
<CreateTraitItem
onRef={(ref) => {
this.child = ref;
}}
availableTraitList={this.state.availableTraitList}
initialValues={{}}
/>
</Modal>
</Spin>
</PageContainer>
</div>
);
}
}

View File

@@ -19,7 +19,6 @@
font-weight: 500;
font-size: 16px;
line-height: 36px;
// color: #fff;
}
p {
margin: 0;

View File

@@ -0,0 +1,108 @@
import React, { useEffect } from 'react';
import G6 from '@antv/g6';
const Topology = () => {
let graph = null;
const data = {
nodes: [
{
id: 'node1',
x: 150,
y: 50,
label: 'node1',
},
{
id: 'node2',
x: 250,
y: 200,
label: 'node2',
},
{
id: 'node3',
x: 100,
y: 350,
label: 'node3',
},
],
edges: [
{
source: 'node1',
target: 'node2',
label: 'edge 1',
},
{
source: 'node2',
target: 'node3',
label: 'edge 2',
},
{
source: 'node3',
target: 'node1',
label: 'edge 3',
},
],
};
const width = 1000;
const height = 400;
useEffect(() => {
if (!graph) {
graph = new G6.Graph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
type: 'circle',
size: [40],
color: '#5B8FF9',
style: {
fill: '#9EC9FF',
lineWidth: 3,
},
labelCfg: {
style: {
fill: '#1890ff',
fontSize: 14,
},
position: 'bottom',
},
},
defaultEdge: {
labelCfg: {
autoRotate: true,
style: {
background: {
fill: '#ffffff',
stroke: '#9EC9FF',
padding: [2, 2, 2, 2],
radius: 2,
},
},
},
},
modes: {
default: ['drag-canvas', 'drag-node'],
},
nodeStateStyles: {
// style configurations for hover state
hover: {
fillOpacity: 0.8,
},
// style configurations for selected state
selected: {
lineWidth: 5,
},
},
});
}
graph.data(data);
graph.render();
}, []);
return <div id="container" style={{ overflow: 'auto', width: '100%', height: '400px' }} />;
};
export default Topology;

View File

@@ -0,0 +1,462 @@
import React, { Fragment } from 'react';
import './index.less';
import { Button, Row, Col, Tabs, Popconfirm, message, Tooltip, Modal, Spin } from 'antd';
import { connect } from 'dva';
import _ from 'lodash';
import CreateTraitItem from '../../../components/AttachOneTrait/index.jsx';
const { TabPane } = Tabs;
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.applist,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
constructor(props) {
super(props);
this.state = {
appDetailData: {},
visible: false,
traitList: [],
availableTraitList: [],
envName: '',
appName: '',
};
}
componentDidMount() {
this.getInitialData();
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.appName !== nextProps.appName) {
this.getInitialData(nextProps.appName);
}
}
getInitialData = async (nextAppName) => {
let appName = _.get(this.props, 'appName', '');
const envName = _.get(this.props, 'envName', '');
appName = nextAppName || appName;
if (appName && envName) {
this.setState({
envName,
appName,
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
envName,
appName,
},
});
if (res) {
this.setState({
appDetailData: res,
});
}
const traits = await this.props.dispatch({
type: 'trait/getTraits',
});
if (traits) {
this.setState({
traitList: traits,
});
}
const workloadType = _.get(res, 'Workload.workload.kind', '');
if (workloadType && workloadType === 'ContainerizedWorkload') {
this.getAcceptTrait('containerized');
} else if (workloadType && workloadType === 'Deployment') {
this.getAcceptTrait('deployment');
}
}
};
getAcceptTrait = (workloadType) => {
const res = this.state.traitList.filter((item) => {
if (item.appliesTo.indexOf(workloadType) !== -1) {
return true;
}
return false;
});
this.setState(() => ({
availableTraitList: res,
}));
};
deleteApp = async (e) => {
e.stopPropagation();
const { envName } = this.state;
const { appDetailData } = this.state;
const appName = _.get(appDetailData, 'Workload.workload.metadata.name', '');
if (appName && envName) {
const res = await this.props.dispatch({
type: 'applist/deleteApp',
payload: {
appName,
envName,
},
});
if (res) {
message.success(res);
this.props.history.push({ pathname: '/ApplicationList' });
}
}
};
deleteTrait = async (e, item) => {
e.stopPropagation();
const { appName, envName } = this.state;
const traitNameObj = _.get(item, 'trait.metadata.annotations', '');
const traitName = traitNameObj['vela.oam.dev/traitDef'] || traitNameObj['trait.oam.dev/name'];
if (traitName && appName && envName) {
const res = await this.props.dispatch({
type: 'trait/deleteOneTrait',
payload: {
envName,
appName,
traitName,
},
});
if (res) {
message.success(res);
this.getInitialData(2);
}
}
};
cancel = (e) => {
e.stopPropagation();
};
createTrait = async () => {
await this.setState({
visible: true,
});
};
handleOk = async () => {
await this.child.validateFields();
const submitData = this.child.getSelectValue();
if (submitData.name) {
const submitObj = {
name: submitData.name,
flags: [],
};
Object.keys(submitData).forEach((currentKey) => {
if (currentKey !== 'name' && submitData[currentKey]) {
submitObj.flags.push({
name: currentKey,
value: submitData[currentKey].toString(),
});
}
});
const { envName, appName } = this.state;
if (envName && appName) {
const res = await this.props.dispatch({
type: 'trait/attachOneTraits',
payload: {
envName,
appName,
params: submitObj,
},
});
if (res) {
this.setState({
visible: false,
});
message.success(res);
this.getInitialData(2);
}
}
} else {
message.warning('please select a trait type');
}
};
handleCancel = () => {
this.setState({
visible: false,
});
};
hrefClick = (e) => {
e.stopPropagation();
};
gotoWorkloadDetail = (e) => {
e.stopPropagation();
};
gotoTraitDetail = (e) => {
e.stopPropagation();
};
render() {
const status = _.get(this.state.appDetailData, 'Status', '');
const Workload = _.get(this.state.appDetailData, 'Workload.workload', {});
const Traits = _.get(this.state.appDetailData, 'Traits', []);
let containers = {};
containers = _.get(Workload, 'spec.containers[0]', {});
let { loadingAll } = this.props;
loadingAll = loadingAll || false;
const colorObj = {
Deployed: '#4CAF51',
Staging: '#F44337',
UNKNOWN: '#1890ff',
};
return (
<div style={{ margin: '8px' }}>
<Spin spinning={loadingAll}>
<div className="card-container app-detial">
<h2>{_.get(Workload, 'metadata.name')}</h2>
<p style={{ marginBottom: '20px' }}>
{Workload.apiVersion}, Kind={Workload.kind}
</p>
<Tabs>
<TabPane tab="Summary" key="1">
<Row>
<Col span="11">
<div
className="summaryBox1"
onClick={(e) => this.gotoWorkloadDetail(e)}
style={{ background: colorObj[status] || '#1890ff' }}
>
<Row>
<Col span="22">
<p className="title">{Workload.kind}</p>
<p>{Workload.apiVersion}</p>
</Col>
<Col span="2">
<p className="title hasCursor" onClick={this.hrefClick}>
?
</p>
</Col>
</Row>
<p className="title">
Name:<span>{_.get(Workload, 'metadata.name')}</span>
</p>
<p className="title">Settings:</p>
<Row>
{Object.keys(containers).map((currentKey) => {
if (currentKey === 'ports') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>{_.get(containers[currentKey], '[0].containerPort', '')}</p>
</Col>
</Fragment>
);
// eslint-disable-next-line no-else-return
} else if (currentKey === 'name') {
return <Fragment key={currentKey} />;
// eslint-disable-next-line no-else-return
} else if (currentKey === 'env') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>env</p>
</Col>
<Col span="16">
<p>{_.get(containers[currentKey], '[0].value', '')}</p>
</Col>
</Fragment>
);
}
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{containers[currentKey]}</p>
</Col>
</Fragment>
);
})}
</Row>
</div>
<Popconfirm
title="Are you sure delete this app?"
onConfirm={(e) => this.deleteApp(e)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
<Button danger>Delete</Button>
</Popconfirm>
</Col>
<Col span="1" />
<Col span="10">
{Traits.length ? (
Traits.map((item, index) => {
const traitItem = _.get(item, 'trait', {});
const annotations = _.get(traitItem, 'metadata.annotations', {});
let traitType = 1;
const spec = _.get(traitItem, 'spec', {});
if (traitItem.kind === 'Ingress') {
traitType = 2;
}
return (
<div
className="summaryBox"
onClick={(e) => this.gotoTraitDetail(e, traitItem)}
key={index.toString()}
>
<Row>
<Col span="22">
<p className="title">{traitItem.kind}</p>
<p>{traitItem.apiVersion}</p>
</Col>
<Col span="2">
<p
className="title hasCursor"
onClick={(e) => {
e.stopPropagation();
}}
>
?
</p>
</Col>
</Row>
<Row>
{Object.keys(annotations).map((currentKey3) => {
return (
<Fragment key={currentKey3}>
<Col span="8">
<p>{currentKey3}:</p>
</Col>
<Col span="8">
<p>{annotations[currentKey3]}</p>
</Col>
</Fragment>
);
})}
</Row>
<p className="title">Properties:</p>
<Row>
{traitType === 2 ? (
<Fragment>
<Col span="8">
<p>domain</p>
</Col>
<Col span="16">
<p>{_.get(spec, 'rules[0].host', '')}</p>
</Col>
<Col span="8">
<p>service</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.serviceName',
'',
)}
</p>
</Col>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.servicePort',
'',
)}
</p>
</Col>
</Fragment>
) : (
Object.keys(spec).map((currentKey) => {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
</Col>
</Fragment>
);
})
)}
</Row>
<div style={{ clear: 'both', height: '32px' }}>
<Popconfirm
title="Are you sure delete this trait?"
onConfirm={(e) => this.deleteTrait(e, item)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
<Button
danger
className="floatRight"
onClick={(e) => {
e.stopPropagation();
}}
>
Delete
</Button>
</Popconfirm>
</div>
</div>
);
})
) : (
<Fragment />
)}
<Tooltip placement="top" title="Attach Trait">
<p
className="hasCursor"
style={{
fontSize: '30px',
display: 'inline-flex',
}}
onClick={this.createTrait}
>
+
</p>
</Tooltip>
</Col>
</Row>
</TabPane>
<TabPane tab="Topology" key="2">
<p>Topology</p>
</TabPane>
</Tabs>
</div>
<Modal
title="Attach a Trait"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Confirm
</Button>,
]}
>
<CreateTraitItem
onRef={(ref) => {
this.child = ref;
}}
availableTraitList={this.state.availableTraitList}
initialValues={{}}
/>
</Modal>
</Spin>
</div>
);
}
}
export default TableList;

View File

@@ -0,0 +1,47 @@
.app-detial {
.ant-tabs {
min-height: 500px;
padding: 10px 20px;
background-color: #fff;
.ant-tabs-content-holder {
background: #fff;
}
}
.summaryBox1,
.summaryBox2,
.summaryBox {
clear: both;
margin-bottom: 20px;
padding: 0 10px 10px;
.title {
font-weight: 500;
font-size: 16px;
line-height: 36px;
}
p {
margin: 0;
font-size: 12px;
line-height: 20px;
}
}
.summaryBox1 {
color: #fff;
background: #0097a7;
cursor: pointer;
}
.summaryBox2 {
color: #fff;
background: #92c47c;
}
.summaryBox {
border: 1px solid black;
cursor: pointer;
}
.hasCursor {
font-size: 20px;
cursor: pointer;
}
.floatRight {
float: right;
}
}

View File

@@ -0,0 +1,126 @@
import React from 'react';
import { Link } from 'umi';
import { Breadcrumb, Button, Menu, Spin } from 'antd';
import { connect } from 'dva';
import _ from 'lodash';
import './index.less';
import ComponentDetail from '../ComponentDetail/index.jsx';
@connect(({ loading }) => ({
loadingAll: loading.models.applist,
}))
class TableList extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
envName: '',
componentName: '',
defaultSelectedKeys: 'a1',
};
}
UNSAFE_componentWillMount() {
let appName = '';
let envName = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
envName,
componentName: appName,
});
if (appName === 'test33') {
this.setState({
defaultSelectedKeys: 'a1',
});
} else if (appName === 'test01') {
this.setState({
defaultSelectedKeys: 'c2',
});
} else {
this.setState({
defaultSelectedKeys: 'c3',
});
}
}
changeComponent = ({ key }) => {
if (key === 'a1') {
this.setState({
componentName: 'test33',
});
} else if (key === 'c2') {
this.setState({
componentName: 'test01',
});
} else {
this.setState({
componentName: 'testoo',
});
}
};
render() {
const { envName, componentName, defaultSelectedKeys } = this.state;
const { loadingAll } = this.props;
return (
<div style={{ height: '100%' }}>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>appname</Breadcrumb.Item>
<Breadcrumb.Item>Components</Breadcrumb.Item>
<Breadcrumb.Item>{componentName}</Breadcrumb.Item>
</Breadcrumb>
</div>
<Spin spinning={loadingAll}>
<div className="appComponent">
<div className="left">
<Menu
mode="inline"
onClick={this.changeComponent}
defaultSelectedKeys={[defaultSelectedKeys]}
>
<Menu.ItemGroup key="g1" title="Components">
<Menu.Item key="a1">a1(containerized)</Menu.Item>
<Menu.Item key="c2">c2(deploy)</Menu.Item>
<Menu.Item key="c3">c3(webserver)</Menu.Item>
</Menu.ItemGroup>
</Menu>
<div className="addComp">
<Link
to={{
// pathname: '/ApplicationList/CreateApplication',
pathname: `/ApplicationList/${componentName}/createComponent`,
state: { appName: componentName, envName },
}}
>
add a new comp
</Link>
</div>
</div>
<div className="right">
<div style={{ margin: '10px', float: 'right' }}>
<Button type="primary">Delete App</Button>
</div>
<ComponentDetail appName={componentName} envName={envName} />
</div>
</div>
</Spin>
</div>
);
}
}
export default TableList;

View File

@@ -0,0 +1,35 @@
.appComponent {
display: flex;
height: calc(100% - 46px);
.left {
width: 200px;
background: #fff;
.ant-menu-item-group-title {
color: #000;
font-weight: 500;
font-size: 18px;
border-top: 1px solid #efefef;
border-bottom: 1px solid #efefef;
}
.ant-menu-item-group-list .ant-menu-item,
.ant-menu-item-group-list .ant-menu-submenu-title {
padding-left: 16px !important;
color: #000;
font-size: 16px;
}
.addComp {
padding: 8px 16px;
color: blue;
font-size: 16px;
text-decoration: underline;
background-color: #fff;
border-top: 1px solid #efefef;
border-bottom: 1px solid #efefef;
cursor: pointer;
}
}
.right {
flex: 1;
// clear: both;
}
}

View File

@@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Button, Row, Col, Form, Input, Select, Steps, message } from 'antd';
import { Button, Row, Col, Form, Input, Select, Steps, message, Breadcrumb } from 'antd';
import { connect } from 'dva';
import { Link } from 'umi';
import _ from 'lodash';
@@ -52,13 +52,6 @@ class TableList extends React.Component {
};
}
UNSAFE_componentWillMount() {
const activeStep = _.get(this.props, 'location.state.activeStep', 0);
this.setState(() => ({
current: activeStep,
}));
}
componentDidMount() {
this.getInitalData();
}
@@ -73,24 +66,6 @@ class TableList extends React.Component {
this.setState({
traitList: traits,
});
// 如果直接跳转到第二步,需要设置值
const traitType = _.get(this.props, 'location.state.TraitType', '');
if (traitType) {
// let availableTraitList = traits.filter((item)=>{
// return item.name === traitType
// })
this.setState({
availableTraitList: traits,
traitNum: [
{
refname: null,
initialData: { name: traitType },
uniq: new Date().valueOf(),
},
],
});
}
if (Array.isArray(res) && res.length) {
this.setState(
() => ({
@@ -98,7 +73,13 @@ class TableList extends React.Component {
}),
() => {
if (this.state.current === 0) {
const WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
let WorkloadType = '';
if (this.props.location.state) {
WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
sessionStorage.setItem('WorkloadType', WorkloadType);
} else {
WorkloadType = sessionStorage.getItem('WorkloadType');
}
this.formRefStep1.current.setFieldsValue({
workload_type: WorkloadType || this.state.workloadList[0].name,
});
@@ -117,7 +98,12 @@ class TableList extends React.Component {
});
};
onFinishStep2 = () => {
onFinishStep2 = async () => {
const asyncValidateArray = [];
this.state.traitNum.forEach((item) => {
asyncValidateArray.push(item.refname.validateFields());
});
await Promise.all(asyncValidateArray);
const newTraitNum = this.state.traitNum.map((item) => {
// eslint-disable-next-line no-param-reassign
item.initialData = item.refname.getSelectValue();
@@ -163,7 +149,11 @@ class TableList extends React.Component {
};
createApp = async () => {
const { step1SubmitObj, traitNum } = this.state;
const { traitNum } = this.state;
const { step1SubmitObj } = this.state;
if (step1SubmitObj.env_name !== this.props.currentEnv) {
step1SubmitObj.env_name = this.props.currentEnv;
}
const submitObj = _.cloneDeep(step1SubmitObj);
const { workload_name: workloadName } = step1SubmitObj;
submitObj.flags.push({
@@ -289,9 +279,6 @@ class TableList extends React.Component {
this.state.traitNum = this.state.traitNum.filter((item) => {
return item.uniq !== uniq;
});
// this.setState(()=>({
// traitNum: this.state.traitNum
// }));
this.setState((prev) => ({
traitNum: prev.traitNum,
}));
@@ -320,6 +307,11 @@ class TableList extends React.Component {
name="workload_name"
label="Name"
rules={[
{
pattern: /^[a-z0-9-_]+$/,
message:
'Names can only use digits(0-9),lowercase letters(a-z),and dashes(-),Underline.',
},
{
required: true,
message: 'Please input name!',
@@ -356,8 +348,11 @@ class TableList extends React.Component {
)}
</Select>
</Form.Item>
<Form.Item label="Settings" />
</div>
<Form.Item
label="Settings"
style={{ background: 'rgba(0, 0, 0, 0.04)', paddingLeft: '16px' }}
/>
<div className="relativeBox">
<p className="hasMore">?</p>
{Array.isArray(workloadSettings) && workloadSettings.length ? (
@@ -365,7 +360,7 @@ class TableList extends React.Component {
if (item.name === 'name') {
return <Fragment key={item.name} />;
}
return (
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
@@ -375,6 +370,22 @@ class TableList extends React.Component {
required: item.required,
message: `Please input ${item.name}!`,
},
{ pattern: /^[0-9]*$/, message: `${item.name} only use digits(0-9).` },
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required,
message: `Please input ${item.name}!`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
@@ -548,11 +559,6 @@ class TableList extends React.Component {
</Row>
</div>
<div className="buttonBox">
{/* <Link to="/ApplicationList">
<Button type="primary" className="floatRight">
Confirm
</Button>
</Link> */}
<Button
type="primary"
className="floatRight"
@@ -570,16 +576,29 @@ class TableList extends React.Component {
);
}
return (
<PageContainer>
<div className="create-container create-app">
<Steps current={current}>
<Step title="Step 1" description="Choose Workload" />
<Step title="Step 2" description="Attach Trait" />
<Step title="Step 3" description="Review and confirm" />
</Steps>
{currentDetail}
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>CreateApplication</Breadcrumb.Item>
</Breadcrumb>
</div>
</PageContainer>
<PageContainer>
<div className="create-container create-app">
<Steps current={current}>
<Step title="Step 1" description="Choose Workload" />
<Step title="Step 2" description="Attach Trait" />
<Step title="Step 3" description="Review and confirm" />
</Steps>
{currentDetail}
</div>
</PageContainer>
</div>
);
}
}

View File

@@ -27,7 +27,6 @@
}
.relativeBox {
position: relative;
// border: 1px solid #eee;
padding: 0 48px 0 16px;
.hasMore {
position: absolute;
@@ -53,7 +52,7 @@
}
.summaryBox1 {
color: #fff;
background: #0097a7;
background: rgb(24, 144, 255);
}
.summaryBox2 {
background: yellow;

View File

@@ -0,0 +1,642 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Button, Row, Col, Form, Input, Select, Steps, message, Breadcrumb } from 'antd';
import { connect } from 'dva';
import { Link } from 'umi';
import _ from 'lodash';
import CreateTraitItem from '../createTrait/index.jsx';
const { Option } = Select;
const { Step } = Steps;
const layout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.workload,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
formRefStep1 = React.createRef();
formRefStep2All = React.createRef();
constructor(props) {
super(props);
this.state = {
current: 0,
isShowMore: false,
traitNum: [
{
refname: null,
initialData: {},
uniq: new Date().valueOf(),
},
],
traitList: [],
availableTraitList: [],
workloadList: [],
workloadSettings: [],
step1SubmitObj: {},
step1InitialValues: {
workload_type: '',
},
step1Settings: [],
appName: '',
envName: '',
};
}
componentDidMount() {
this.getInitalData();
}
getInitalData = async () => {
let appName = '';
let envName = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
appName,
envName,
});
const res = await this.props.dispatch({
type: 'workload/getWorkload',
});
const traits = await this.props.dispatch({
type: 'trait/getTraits',
});
this.setState({
traitList: traits,
});
if (Array.isArray(res) && res.length) {
this.setState(
() => ({
workloadList: res,
}),
() => {
if (this.state.current === 0) {
let WorkloadType = '';
if (this.props.location.state) {
WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
sessionStorage.setItem('WorkloadType', WorkloadType);
} else {
WorkloadType = sessionStorage.getItem('WorkloadType');
}
this.formRefStep1.current.setFieldsValue({
workload_type: WorkloadType || this.state.workloadList[0].name,
});
this.workloadTypeChange(this.state.workloadList[0].name);
}
},
);
}
};
onFinishStep1 = (values) => {
this.setState({
current: 1,
step1InitialValues: values,
isShowMore: false,
});
};
onFinishStep2 = async () => {
const asyncValidateArray = [];
this.state.traitNum.forEach((item) => {
asyncValidateArray.push(item.refname.validateFields());
});
await Promise.all(asyncValidateArray);
const newTraitNum = this.state.traitNum.map((item) => {
// eslint-disable-next-line no-param-reassign
item.initialData = item.refname.getSelectValue();
return item;
});
// 进行trait数据整理便于第三步展示
this.setState(() => ({
traitNum: newTraitNum,
current: 2,
}));
};
gotoStep2 = () => {
this.setState({
current: 1,
isShowMore: false,
});
};
gotoStep1 = () => {
this.setState({
current: 0,
});
};
changeShowMore = () => {
this.setState({
isShowMore: true,
});
};
addMore = (e) => {
e.preventDefault();
this.setState((prev) => ({
traitNum: prev.traitNum.concat([
{
refname: null,
initialData: {},
uniq: new Date().valueOf(),
},
]),
}));
};
createApp = async () => {
const { traitNum } = this.state;
const { step1SubmitObj } = this.state;
if (step1SubmitObj.env_name !== this.props.currentEnv) {
step1SubmitObj.env_name = this.props.currentEnv;
}
const submitObj = _.cloneDeep(step1SubmitObj);
const { workload_name: workloadName } = step1SubmitObj;
submitObj.flags.push({
name: 'name',
value: workloadName.toString(),
});
// 处理数据为提交的格式
if (traitNum.length) {
const { env_name: envName } = step1SubmitObj;
const step2SubmitObj = [];
traitNum.forEach(({ initialData }) => {
if (initialData.name) {
const initialObj = {
name: initialData.name,
env_name: envName,
workload_name: workloadName,
flags: [],
};
Object.keys(initialData).forEach((key) => {
if (key !== 'name' && initialData[key]) {
initialObj.flags.push({
name: key,
value: initialData[key].toString(),
});
}
});
step2SubmitObj.push(initialObj);
}
});
submitObj.traits = step2SubmitObj;
}
const res = await this.props.dispatch({
type: 'workload/createWorkload',
payload: {
params: submitObj,
},
});
if (res) {
message.success(res);
const { appName, envName } = this.state;
this.props.history.push({
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
});
}
};
createWorkload = async () => {
await this.formRefStep1.current.validateFields();
const currentData = this.formRefStep1.current.getFieldsValue();
const submitObj = {
env_name: this.props.currentEnv,
workload_type: currentData.workload_type,
workload_name: currentData.workload_name,
flags: [],
};
Object.keys(currentData).forEach((key) => {
if (key !== 'workload_name' && key !== 'workload_type' && currentData[key]) {
submitObj.flags.push({
name: key,
value: currentData[key].toString(),
});
}
});
this.setState({
current: 1,
step1InitialValues: currentData,
step1Settings: submitObj.flags,
step1SubmitObj: submitObj,
});
this.getAcceptTrait(currentData.workload_type);
};
workloadTypeChange = (value) => {
const content = this.formRefStep1.current.getFieldsValue();
this.formRefStep1.current.resetFields();
const initialObj = {
workload_type: content.workload_type,
workload_name: content.workload_name,
};
this.formRefStep1.current.setFieldsValue(initialObj);
const currentWorkloadSetting = this.state.workloadList.filter((item) => {
return item.name === value;
});
if (currentWorkloadSetting.length) {
this.setState(
{
workloadSettings: currentWorkloadSetting[0].parameters,
},
() => {
this.state.workloadSettings.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep1.current.setFieldsValue(initialObj);
},
);
}
this.setState({
traitNum: [
{
refname: null,
initialData: {},
uniq: new Date().valueOf(),
},
],
});
};
getAcceptTrait = (workloadType) => {
const res = this.state.traitList.filter((item) => {
if (item.appliesTo.indexOf(workloadType) !== -1) {
return true;
}
return false;
});
this.setState(() => ({
availableTraitList: res,
}));
};
deleteTraitItem = (uniq) => {
// 删除的时候不要依据数组的index删除,要一个唯一性的值
this.state.traitNum = this.state.traitNum.filter((item) => {
return item.uniq !== uniq;
});
this.setState((prev) => ({
traitNum: prev.traitNum,
}));
};
render() {
const { appName, envName } = this.state;
const { current, step1InitialValues, traitNum, workloadSettings } = this.state;
let { workloadList } = this.state;
workloadList = Array.isArray(workloadList) ? workloadList : [];
let currentDetail;
if (current === 0) {
currentDetail = (
<div>
<div className="minBox">
<Form
initialValues={step1InitialValues}
labelAlign="left"
{...layout}
ref={this.formRefStep1}
name="control-ref"
onFinish={this.onFinishStep1}
style={{ width: '60%' }}
>
<div style={{ padding: '16px 48px 0px 16px' }}>
<Form.Item
name="workload_name"
label="Name"
rules={[
{
pattern: /^[a-z0-9-_]+$/,
message:
'Names can only use digits(0-9),lowercase letters(a-z),and dashes(-),Underline.',
},
{
required: true,
message: 'Please input name!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="workload_type"
label="Workload Type"
rules={[
{
required: true,
message: 'Please select Workload Type!',
},
]}
>
<Select
placeholder="Select a Workload Type"
allowClear
onChange={this.workloadTypeChange}
>
{workloadList.length ? (
workloadList.map((item) => {
return (
<Option value={item.name} key={item.name}>
{item.name}
</Option>
);
})
) : (
<></>
)}
</Select>
</Form.Item>
</div>
<Form.Item
label="Settings"
style={{ background: 'rgba(0, 0, 0, 0.04)', paddingLeft: '16px' }}
/>
<div className="relativeBox">
<p className="hasMore">?</p>
{Array.isArray(workloadSettings) && workloadSettings.length ? (
workloadSettings.map((item) => {
if (item.name === 'name') {
return <Fragment key={item.name} />;
}
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required,
message: `Please input ${item.name}!`,
},
{ pattern: /^[0-9]*$/, message: `${item.name} only use digits(0-9).` },
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required,
message: `Please input ${item.name}!`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
</Form.Item>
);
})
) : (
<></>
)}
</div>
<div className="buttonBox">
<Button type="primary" className="floatRightGap" onClick={this.createWorkload}>
Next
</Button>
<Link
to={{
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
}}
>
<Button className="floatRightGap">Cancle</Button>
</Link>
</div>
</Form>
</div>
</div>
);
} else if (current === 1) {
currentDetail = (
<div>
<div className="minBox" style={{ width: '60%' }}>
<div style={{ padding: '0px 48px 0px 16px', width: '60%' }}>
<p style={{ fontSize: '18px', lineHeight: '32px' }}>
Name:<span>{step1InitialValues.workload_name}</span>
</p>
</div>
<div style={{ border: '1px solid #eee', padding: '16px 48px 16px 16px' }}>
<p className="title">{step1InitialValues.workload_type}</p>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>apps/v1</span>
<span
style={{
color: '#1890ff',
cursor: 'pointer',
display: this.state.isShowMore ? 'none' : 'black',
}}
onClick={this.changeShowMore}
>
more...
</span>
</div>
{this.state.isShowMore ? (
<div>
<p className="title" style={{ marginTop: '16px' }}>
Settings:
</p>
<Row>
{this.state.step1Settings.map((item) => {
return (
<Fragment key={item.name}>
<Col span="8">
<p>{item.name}:</p>
</Col>
<Col span="16">
<p>{item.value}</p>
</Col>
</Fragment>
);
})}
</Row>
</div>
) : (
''
)}
</div>
<div ref={this.formRefStep2All}>
{traitNum.map((item) => {
return (
<CreateTraitItem
onRef={(ref) => {
// eslint-disable-next-line no-param-reassign
item.refname = ref;
}}
key={item.uniq.toString()}
availableTraitList={this.state.availableTraitList}
uniq={item.uniq}
initialValues={item.initialData}
deleteTraitItem={this.deleteTraitItem}
/>
);
})}
</div>
<button style={{ marginTop: '16px' }} onClick={this.addMore} type="button">
Add More...
</button>
<div className="buttonBox">
<Button type="primary" className="floatRight" onClick={this.onFinishStep2}>
Next
</Button>
<Button className="floatRightGap" onClick={this.gotoStep1}>
Back
</Button>
</div>
</div>
</div>
);
} else {
currentDetail = (
<div>
<div className="minBox">
<p>
Name:<span>{step1InitialValues.workload_name}</span>
</p>
<Row>
<Col span="11">
<div className="summaryBox1">
<Row>
<Col span="22">
<p className="title">{step1InitialValues.workload_type}</p>
<p>apps/v1</p>
</Col>
</Row>
<p className="title hasMargin">Settings:</p>
<Row>
{this.state.step1Settings.map((item) => {
return (
<Fragment key={item.name}>
<Col span="8">
<p>{item.name}:</p>
</Col>
<Col span="16">
<p>{item.value}</p>
</Col>
</Fragment>
);
})}
</Row>
</div>
</Col>
<Col span="1" />
<Col span="10">
{traitNum.map(({ initialData }, index) => {
if (initialData.name) {
return (
<div className="summaryBox" key={index.toString()}>
<Row>
<Col span="22">
<p className="title">{initialData.name}</p>
<p>core.oam.dev/v1alpha2</p>
</Col>
</Row>
<p className="title hasMargin">Properties:</p>
<Row>
{Object.keys(initialData).map((currentKey) => {
if (currentKey !== 'name') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}:</p>
</Col>
<Col span="16">
<p>{initialData[currentKey]}</p>
</Col>
</Fragment>
);
}
return <Fragment key={currentKey} />;
})}
</Row>
</div>
);
}
return <Fragment key={index.toString()} />;
})}
</Col>
</Row>
</div>
<div className="buttonBox">
<Button
type="primary"
className="floatRight"
onClick={() => {
this.createApp();
}}
>
Confirm
</Button>
<Button className="floatRightGap" onClick={this.gotoStep2}>
Back
</Button>
</div>
</div>
);
}
return (
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link
to={{
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
}}
>
{' '}
{appName}
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>createComponent</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<div className="create-container create-app">
<Steps current={current}>
<Step title="Step 1" description="Choose Workload" />
<Step title="Step 2" description="Attach Trait" />
<Step title="Step 3" description="Review and confirm" />
</Steps>
{currentDetail}
</div>
</PageContainer>
</div>
);
}
}
export default TableList;

View File

@@ -0,0 +1,66 @@
.create-container {
padding: 10px;
background: #fff;
}
.create-app {
.minBox {
margin: 20px 10px;
padding: 10px;
border: 1px solid #eee;
.title {
font-size: 16px;
line-height: 36px;
}
}
.buttonBox {
clear: both;
height: 42px;
margin: 0 10px;
padding: 10px;
}
.floatRight {
float: right;
}
.floatRightGap {
float: right;
margin-right: 16px;
}
.relativeBox {
position: relative;
padding: 0 48px 0 16px;
.hasMore {
position: absolute;
top: 0;
right: 16px;
padding: 4px;
color: rgb(24, 144, 255);
font-size: 16px;
cursor: pointer;
}
}
.summaryBox1,
.summaryBox2,
.summaryBox {
clear: both;
margin-bottom: 20px;
padding: 0 10px 10px;
p {
margin: 0;
font-size: 12px;
line-height: 20px;
}
}
.summaryBox1 {
color: #fff;
background: rgb(24, 144, 255);
}
.summaryBox2 {
background: yellow;
}
.summaryBox {
border: 1px solid black;
}
.hasMargin {
padding: 8px 0;
}
}

View File

@@ -35,29 +35,35 @@ export default class CreateTraitItem extends React.PureComponent {
return this.formRefStep2.current.getFieldsValue();
};
validateFields = () => {
return this.formRefStep2.current.validateFields();
};
traitSelectChange = async (value, isType = 1) => {
const res = await this.props.dispatch({
type: 'trait/getTraitByName',
payload: {
traitName: value,
},
});
this.setState({
parameters: res.parameters,
});
if (isType === 2) {
this.formRefStep2.current.setFieldsValue(this.props.initialValues);
} else {
// 进行默认值填写
const parameters = _.get(res, 'parameters', []);
if (parameters.length) {
const initialObj = {};
parameters.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep2.current.setFieldsValue(initialObj);
if (value) {
const res = await this.props.dispatch({
type: 'trait/getTraitByName',
payload: {
traitName: value,
},
});
this.setState({
parameters: res.parameters,
});
if (isType === 2) {
this.formRefStep2.current.setFieldsValue(this.props.initialValues);
} else {
// 进行默认值填写
const parameters = _.get(res, 'parameters', []);
if (parameters.length) {
const initialObj = {};
parameters.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep2.current.setFieldsValue(initialObj);
}
}
}
};
@@ -78,8 +84,12 @@ export default class CreateTraitItem extends React.PureComponent {
>
<div style={{ border: '1px solid #eee', margin: '16px 0px 8px' }}>
<div style={{ padding: '16px 48px 0px 16px' }}>
<Form.Item name="name" label="Trait">
<Select placeholder="Select a Trait" allowClear onChange={this.traitSelectChange}>
<Form.Item
name="name"
label="Trait"
rules={[{ required: true, message: 'Please Select a Trait!' }]}
>
<Select placeholder="Select a Trait" onChange={this.traitSelectChange}>
{availableTraitList.map((item) => {
return (
<Option value={item.name} key={item.name}>
@@ -92,11 +102,36 @@ export default class CreateTraitItem extends React.PureComponent {
<Form.Item label="Properties" />
</div>
<div className="relativeBox">
{/* <p className="hasMore">?</p> */}
{this.state.parameters ? (
this.state.parameters.map((item) => {
return (
<Form.Item name={item.name} label={item.name} key={item.name}>
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[0-9]*$/, message: `${item.name} only use digits(0-9).` },
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required || false,
message: `Please input ${item.name} !`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
</Form.Item>
);

View File

@@ -1,33 +1,41 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { SearchOutlined, BranchesOutlined, ApartmentOutlined } from '@ant-design/icons';
import { Button, Card, Row, Col, Form, Select, DatePicker, Spin, Empty } from 'antd';
import { BranchesOutlined, ApartmentOutlined } from '@ant-design/icons';
import { Button, Card, Row, Col, Form, Spin, Empty, Breadcrumb, Modal, Input } from 'antd';
import { connect } from 'dva';
import moment from 'moment';
import './index.less';
import { Link } from 'umi';
const { Option } = Select;
const layout = {
labelCol: {
span: 6,
},
wrapperCol: {
span: 18,
},
};
@connect(({ loading, applist, globalData }) => ({
loadingAll: loading.models.applist,
// 当applist这个models有数据请求行为的时候loading为true没有请求的时候为false
// loadingList: loading.effects['applist/getList'],
// 当applist的effects中的getList有异步请求行为时为true没有请求行为时为false
returnObj: applist.returnObj,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
formRef = React.createRef();
constructor(props) {
super(props);
this.state = {};
this.state = {
visible: false,
};
}
componentDidMount() {
const { currentEnv } = this.props;
if (currentEnv) {
this.props.dispatch({
type: 'applist/getList', // applist对应models层的命名空间namespace
type: 'applist/getList',
payload: {
url: `/api/envs/${currentEnv}/apps/`,
},
@@ -40,29 +48,41 @@ class TableList extends React.Component {
return true;
}
this.props.dispatch({
type: 'applist/getList', // applist对应models层的命名空间namespace
type: 'applist/getList',
payload: {
url: `/api/envs/${nextProps.currentEnv}/apps/`,
},
});
return true;
// return true;
}
onFinish = () => {
// const data = moment(values.createTime).format('YYYY-MM-DD')
showModal = () => {
this.setState({
visible: true,
});
};
handleOk = async () => {
await this.formRef.current.validateFields();
// const submitData = await this.formRef.current.validateFields();
};
handleCancel = () => {
this.setState({
visible: false,
});
};
onFinish = () => {};
handleChange = () => {};
handleAdd = () => {};
onSelect = () => {
// console.log("selected", selectedKeys, info);
};
onSelect = () => {};
getHeight = (num) => {
return `${num * 55}px`;
return `${num * 43}px`;
};
getFormatDate = (time) => {
@@ -80,98 +100,125 @@ class TableList extends React.Component {
UNKNOWN: 'first3',
};
return (
<PageContainer>
<Spin spinning={loadingAll}>
<div className="applist">
<Form name="horizontal_login" layout="inline" onFinish={this.onFinish}>
<Form.Item name="createTime">
<DatePicker placeholder="createTime" />
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Applications</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Spin spinning={loadingAll}>
<div className="applist">
<Form name="horizontal_login" layout="inline" onFinish={this.onFinish}>
<Form.Item>
<Link to="/ApplicationList/CreateApplication">
<Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
create
</Button>
</Link>
</Form.Item>
</Form>
</div>
<Row gutter={16}>
{Array.isArray(returnObj) && returnObj.length ? (
returnObj.map((item, index) => {
const { traits = [] } = item;
return (
<Col span={6} onClick={this.gotoDetail} key={index.toString()}>
<Link
to={{
pathname: '/ApplicationList/ApplicationListDetail',
state: { appName: item.name, envName: currentEnv },
}}
>
<Card
title={item.name}
bordered={false}
extra={this.getFormatDate(item.created)}
>
<div className="cardContent">
<div
className="box2"
style={{ height: this.getHeight(traits.length) }}
/>
<div className="box1">
{traits.length ? (
<div className="box3" style={{ width: '30px' }} />
) : (
''
)}
<div
className={['hasPadding', colorObj[item.status] || 'first3'].join(
' ',
)}
>
<ApartmentOutlined style={{ marginRight: '4px' }} />
{item.workload}
</div>
</div>
{traits.map((item1, index1) => {
return (
<div className="box1" key={index1.toString()}>
<div className="box3" style={{ width: '50px' }} />
<div className="other hasPadding">
<BranchesOutlined style={{ marginRight: '4px' }} />
{item1}
</div>
</div>
);
})}
</div>
</Card>
</Link>
</Col>
);
})
) : (
<div style={{ width: '100%', height: '80%' }}>
<Empty />
</div>
)}
</Row>
</Spin>
<Modal
title="Create Application"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="submit" type="primary" onClick={this.handleOk}>
Create
</Button>,
]}
>
<Form {...layout} ref={this.formRef} name="control-ref" labelAlign="left">
<Form.Item
name="name"
label="Name"
rules={[
{
required: true,
message: 'Please input application name!',
},
{
pattern: /^[a-z0-9-_]+$/,
message:
'Name can only use digits(0-9),lowercase letters(a-z),and dashes(-),Underline.',
},
]}
>
<Input />
</Form.Item>
<Form.Item name="status">
<Select
placeholder="status"
style={{ width: 120 }}
onChange={this.handleChange}
allowClear
>
<Option value="True">True</Option>
<Option value="False">False</Option>
<Option value="UNKNOWN">UNKNOWN</Option>
</Select>
</Form.Item>
<Form.Item>
<Button icon={<SearchOutlined />} htmlType="submit">
Search
</Button>
</Form.Item>
<Form.Item>
<Link to="/ApplicationList/CreateApplication">
<Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
create
</Button>
</Link>
<Form.Item name="description" label="Description">
<Input.TextArea />
</Form.Item>
</Form>
</div>
<Row gutter={16}>
{Array.isArray(returnObj) && returnObj.length ? (
returnObj.map((item, index) => {
const { traits = [] } = item;
return (
<Col span={6} onClick={this.gotoDetail} key={index.toString()}>
<Link
to={{
pathname: '/ApplicationList/ApplicationListDetail',
state: { appName: item.name, envName: currentEnv },
}}
>
<Card
title={item.name}
bordered={false}
extra={this.getFormatDate(item.created)}
>
<div className="cardContent">
<div className="box2" style={{ height: this.getHeight(traits.length) }} />
<div className="box1">
{traits.length ? (
<div className="box3" style={{ width: '40px' }} />
) : (
''
)}
<div
className={['hasPadding', colorObj[item.status] || 'first3'].join(
' ',
)}
>
<ApartmentOutlined style={{ marginRight: '10px' }} />
{item.workload}
</div>
</div>
{traits.map((item1, index1) => {
return (
<div className="box1" key={index1.toString()}>
<div className="box3" style={{ width: '80px' }} />
<div className="other hasPadding">
<BranchesOutlined style={{ marginRight: '10px' }} />
{item1}
</div>
</div>
);
})}
</div>
</Card>
</Link>
</Col>
);
})
) : (
<div style={{ width: '100%', height: '80%' }}>
<Empty />
</div>
)}
</Row>
</Spin>
</PageContainer>
</Modal>
</PageContainer>
</div>
);
}
}

View File

@@ -15,38 +15,38 @@
}
.box2 {
position: absolute;
top: 21px;
top: 15px;
width: 0;
padding: 0;
border-left: 1px solid black;
}
.box3 {
position: absolute;
top: 21px;
top: 15px;
height: 0;
border-bottom: 1px solid black;
}
.hasPadding {
padding: 10px;
padding: 4px;
color: #fff;
}
div {
margin-bottom: 10px;
}
.first1 {
margin-left: 40px;
background: #1890ff;
margin-left: 30px;
background: #4caf51;
}
.first2 {
margin-left: 40px;
background: red;
margin-left: 30px;
background: #f44337;
}
.first3 {
margin-left: 40px;
background: #92c47c;
margin-left: 30px;
background: #1890ff;
}
.other {
margin-left: 80px;
background: #ee9611;
margin-left: 50px;
background: #ffc105;
}
}

View File

@@ -1,13 +1,13 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Space, Button, Row, Col, message, Spin } from 'antd';
// import { Space, Modal, Button, Row, Col, message, Spin } from 'antd';
// import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Space, Button, Row, Col, message, Spin, Breadcrumb, Modal } from 'antd';
import { Link } from 'umi';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import './index.less';
import { connect } from 'dva';
import _ from 'lodash';
// const { confirm } = Modal;
const { confirm } = Modal;
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.capability,
@@ -19,6 +19,7 @@ class TableList extends React.PureComponent {
this.state = {
workloadList: [],
traitList: [],
capabilityCenterName: '',
};
}
@@ -34,7 +35,16 @@ class TableList extends React.PureComponent {
const workloadList = [];
const traitList = [];
if (Array.isArray(res)) {
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
let capabilityCenterName = '';
if (this.props.location.state) {
capabilityCenterName = _.get(this.props, 'location.state.name', '');
sessionStorage.setItem('capabilityCenterName', capabilityCenterName);
} else {
capabilityCenterName = sessionStorage.getItem('capabilityCenterName');
}
this.setState({
capabilityCenterName,
});
res.forEach((item) => {
if (item.center === capabilityCenterName) {
if (item.type === 'workload') {
@@ -53,13 +63,12 @@ class TableList extends React.PureComponent {
};
gotoOtherPage = () => {
// window.location.href = 'https://github.com/oam-dev/catalog/blob/master/workloads/cloneset/README.md';
window.open('https://github.com/oam-dev/catalog/blob/master/workloads/cloneset/README.md');
// window.open('https://github.com/oam-dev/catalog/blob/master/workloads/cloneset/README.md');
};
installSignle = async (e, name) => {
e.stopPropagation();
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
const { capabilityCenterName } = this.state;
const res = await this.props.dispatch({
type: 'capability/syncOneCapability',
payload: {
@@ -69,32 +78,34 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
// this.getInitialData();
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
};
uninstallSignle = async (e, name) => {
e.stopPropagation();
// const capabilityCenterName = _.get(this.props, 'location.state.name', '');
if (name) {
const res = await this.props.dispatch({
type: 'capability/deleteOneCapability',
payload: {
// capabilityCenterName,
capabilityName: name,
},
});
if (res) {
message.success(res);
// this.getInitialData();
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
}
};
syncAllSignle = async () => {
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
const { capabilityCenterName } = this.state;
if (capabilityCenterName) {
const res = await this.props.dispatch({
type: 'capability/syncCapability',
@@ -104,56 +115,51 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
// this.getInitialData();
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
}
};
showDeleteConfirm = () => {
message.info('正在开发中...');
// // eslint-disable-next-line
// const _this = this;
// const capabilityCenterName = _.get(this.props, 'location.state.name', '');
// if (capabilityCenterName) {
// confirm({
// title: `Are you sure delete ${capabilityCenterName}?`,
// icon: <ExclamationCircleOutlined />,
// width: 500,
// content: (
// <div>
// <p style={{ margin: '0px' }}>您本次移除 {capabilityCenterName},将会删除的应用列表:</p>
// <Space>
// <span>abc</span>
// <span>abc</span>
// <span>abc</span>
// <span>abc</span>
// </Space>
// <p style={{ margin: '0px' }}>
// 确认后,移除 {capabilityCenterName},并且删除相应的应用?
// </p>
// </div>
// ),
// okText: 'Yes',
// okType: 'danger',
// cancelText: 'No',
// async onOk() {
// const res = await _this.props.dispatch({
// type: 'capability/deleteCapability',
// payload: {
// capabilityName: capabilityCenterName,
// },
// });
// if (res) {
// message.success(res);
// _this.props.history.push({ pathname: '/Capability' });
// }
// },
// onCancel() {
// // console.log('Cancel');
// },
// });
// }
// eslint-disable-next-line
const _this = this;
const { capabilityCenterName } = this.state;
if (capabilityCenterName) {
confirm({
title: `Are you sure delete ${capabilityCenterName}?`,
icon: <ExclamationCircleOutlined />,
width: 500,
content: (
<div>
<p style={{ margin: '0px' }}>您本次移除 {capabilityCenterName}将会删除的应用列表</p>
<p style={{ margin: '0px' }}>
确认后移除 {capabilityCenterName}并且删除相应的应用
</p>
</div>
),
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
const res = await _this.props.dispatch({
type: 'capability/deleteCapability',
payload: {
capabilityCenterName,
},
});
if (res) {
message.success(res);
_this.props.history.push({ pathname: '/Capability' });
}
},
onCancel() {
// console.log('Cancel');
},
});
}
};
render() {
@@ -161,82 +167,101 @@ class TableList extends React.PureComponent {
let { loadingAll } = this.props;
loadingAll = loadingAll || false;
return (
<PageContainer>
<Spin spinning={loadingAll}>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={this.syncAllSignle}>
Install all
</Button>
<Button type="default" onClick={this.showDeleteConfirm}>
Remove
</Button>
</Space>
</div>
<div>
<h3>Workloads</h3>
<Row>
{workloadList.length ? (
workloadList.map((item) => {
return (
<Col span="4" key={item.name}>
<div className="itemBox" onClick={this.gotoOtherPage}>
<img
src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1109866916,1852667152&fm=26&gp=0.jpg"
alt="workload"
/>
<p>{item.name}</p>
{item.status === 'installed' ? (
<Button onClick={(e) => this.uninstallSignle(e, item.name)}>
uninstall
</Button>
) : (
<Button onClick={(e) => this.installSignle(e, item.name)}>install</Button>
)}
</div>
</Col>
);
})
) : (
<Fragment>
<div>暂无可用的workload</div>
</Fragment>
)}
</Row>
</div>
<div>
<h3>Traits</h3>
<Row>
{traitList.length ? (
traitList.map((item) => {
return (
<Col span="4" key={item.name}>
<div className="itemBox" onClick={this.gotoOtherPage}>
<img
src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1109866916,1852667152&fm=26&gp=0.jpg"
alt="workload"
/>
<p>{item.name}</p>
{item.status === 'installed' ? (
<Button onClick={(e) => this.uninstallSignle(e, item.name)}>
uninstall
</Button>
) : (
<Button onClick={(e) => this.installSignle(e, item.name)}>install</Button>
)}
</div>
</Col>
);
})
) : (
<Fragment>
<div>暂无可用的trait</div>
</Fragment>
)}
</Row>
</div>
</Spin>
</PageContainer>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/Capability">Capability</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Detail</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Spin spinning={loadingAll}>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={this.syncAllSignle}>
Install all
</Button>
<Button type="default" onClick={this.showDeleteConfirm}>
Remove
</Button>
</Space>
</div>
<div>
<h3>Workloads</h3>
<Row>
{workloadList.length ? (
workloadList.map((item) => {
return (
<Col span="4" key={item.name}>
<div className="itemBox" onClick={this.gotoOtherPage}>
<div className="title">{item.name.substr(0, 3).toUpperCase()}</div>
<p>{item.name}</p>
{item.status === 'installed' ? (
<Button onClick={(e) => this.uninstallSignle(e, item.name)}>
uninstall
</Button>
) : (
<Button
onClick={(e) => this.installSignle(e, item.name)}
type="primary"
ghost
>
install
</Button>
)}
</div>
</Col>
);
})
) : (
<Fragment>
<div>暂无可用的workload</div>
</Fragment>
)}
</Row>
</div>
<div>
<h3>Traits</h3>
<Row>
{traitList.length ? (
traitList.map((item) => {
return (
<Col span="4" key={item.name}>
<div className="itemBox" onClick={this.gotoOtherPage}>
<div className="title">{item.name.substr(0, 3).toUpperCase()}</div>
<p>{item.name}</p>
{item.status === 'installed' ? (
<Button onClick={(e) => this.uninstallSignle(e, item.name)}>
uninstall
</Button>
) : (
<Button
onClick={(e) => this.installSignle(e, item.name)}
type="primary"
ghost
>
install
</Button>
)}
</div>
</Col>
);
})
) : (
<Fragment>
<div>暂无可用的trait</div>
</Fragment>
)}
</Row>
</div>
</Spin>
</PageContainer>
</div>
);
}
}

View File

@@ -3,7 +3,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
margin: 10px;
margin: 10px 16px 10px 0;
padding: 10px 0;
background-color: #fff;
cursor: pointer;
@@ -11,6 +11,12 @@
width: 60%;
margin: 10px auto;
}
.title {
width: 60%;
margin: 10px auto;
font-size: 30px;
text-align: center;
}
p {
margin: 0;
margin-bottom: 10px;

View File

@@ -1,14 +1,14 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Table, Space, Modal, Form, Input, message, Spin } from 'antd';
// import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Table, Space, Modal, Form, Input, message, Spin, Breadcrumb } from 'antd';
import { CopyOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Link } from 'umi';
import './index.less';
import { connect } from 'dva';
import _ from 'lodash';
// const { confirm } = Modal;
const { Column } = Table;
const { confirm } = Modal;
const layout = {
labelCol: {
@@ -32,6 +32,7 @@ class TableList extends React.PureComponent {
this.state = {
visible: false,
capabilityList: [],
isCreateLoading: false,
};
}
@@ -56,14 +57,19 @@ class TableList extends React.PureComponent {
}
};
showModal = () => {
this.setState({
showModal = async () => {
await this.setState({
visible: true,
isCreateLoading: false,
});
this.formRef.current.resetFields();
};
handleOk = async () => {
const submitData = await this.formRef.current.validateFields();
this.setState({
isCreateLoading: true,
});
const res = await this.props.dispatch({
type: 'capability/createCapabilityCenter',
payload: {
@@ -85,13 +91,6 @@ class TableList extends React.PureComponent {
}
};
// handleTest = async () => {
// await this.formRef.current.validateFields();
// this.setState({
// visible: false,
// });
// };
handleCancel = () => {
this.setState({
visible: false,
@@ -113,58 +112,63 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
// this.getInitialData();
}
const newList1 = _.cloneDeep(this.state.capabilityList);
newList1[index].btnSyncLoading = false;
this.setState(() => ({
capabilityList: newList1,
}));
window.location.reload();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
};
showDeleteConfirm = () => {
message.info('正在开发中...');
// if (record) {
// // eslint-disable-next-line
// const _this = this;
// confirm({
// title: `Are you sure delete ${record}?`,
// icon: <ExclamationCircleOutlined />,
// width: 500,
// content: (
// <div>
// <p>您本次移除 {record},将会删除的应用列表:</p>
// <Space>
// <span>abc</span>
// <span>abc</span>
// <span>abc</span>
// <span>abc</span>
// </Space>
// <p>确认后,移除{record},并且删除相应的应用?</p>
// </div>
// ),
// okText: 'Yes',
// okType: 'danger',
// cancelText: 'No',
// async onOk() {
// const res = await _this.props.dispatch({
// type: 'capability/deleteCapability',
// payload: {
// capabilityName: record,
// },
// });
// if (res) {
// message.success(res);
// _this.getInitialData();
// }
// },
// onCancel() {
// // console.log('Cancel');
// },
// });
// }
copyURL = (text) => {
const oInput = document.createElement('input');
oInput.value = text;
document.body.appendChild(oInput);
oInput.select();
document.execCommand('Copy');
oInput.className = 'oInput';
oInput.style.display = 'none';
message.success('copy success');
};
showDeleteConfirm = (record) => {
if (record) {
// eslint-disable-next-line
const _this = this;
confirm({
title: `Are you sure delete ${record}?`,
icon: <ExclamationCircleOutlined />,
width: 500,
content: (
<div>
<p>您本次移除 {record}将会删除的应用列表</p>
<p>确认后移除{record}并且删除相应的应用</p>
</div>
),
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
const res = await _this.props.dispatch({
type: 'capability/deleteCapability',
payload: {
capabilityCenterName: record,
},
});
if (res) {
message.success(res);
_this.getInitialData();
}
},
onCancel() {
// console.log('Cancel');
},
});
}
};
render() {
@@ -172,107 +176,130 @@ class TableList extends React.PureComponent {
let { loadingList } = this.props;
loadingList = loadingList || false;
capabilityList = Array.isArray(capabilityList) ? capabilityList : [];
const { isCreateLoading } = this.state;
return (
<PageContainer>
<Spin spinning={loadingList}>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={this.showModal}>
Create
</Button>
{/* <Button type="default">Sync All</Button> */}
</Space>
</div>
<Modal
title="Create Capability Center"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
// <Button key="test" onClick={this.handleTest}>
// Test
// </Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Create
</Button>,
]}
>
<Form {...layout} ref={this.formRef} name="control-ref" labelAlign="left">
<Form.Item
name="Name"
label="Name"
rules={[
{
required: true,
message: 'Please input name!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="Address"
label="URL"
rules={[
// { pattern: '/^((https|http|ftp|rtsp|mms){0,1}(:\/\/){0,1})\.(([A-Za-z0-9-~]+)\.)+([A-Za-z0-9-~\/])+$/',
// message: 'please input correct URL'
// },
{
required: true,
message: 'Please input URL!',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table dataSource={capabilityList} pagination={false} rowKey={(record) => record.name}>
<Column
title="Name"
dataIndex="name"
key="name"
render={(text, record) => {
return (
<Link to={{ pathname: '/Capability/Detail', state: { name: record.name } }}>
{text}
</Link>
);
}}
/>
<Column
title="URL"
dataIndex="url"
key="url"
render={(text) => {
return (
<a href={text} target="_blank" rel="noreferrer">
{text}
</a>
);
}}
/>
<Column
title="Operations"
dataIndex="name"
key="name"
render={(text, record, index) => {
return (
<Space>
<Button
loading={record.btnSyncLoading}
onClick={() => this.syncSignle(text, index)}
>
sync
</Button>
<Button onClick={() => this.showDeleteConfirm(text)}>remove</Button>
</Space>
);
}}
/>
</Table>
</Spin>
</PageContainer>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Capability</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Spin spinning={loadingList}>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={this.showModal}>
Create
</Button>
</Space>
</div>
<Modal
title="Create Capability Center"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button
loading={isCreateLoading}
key="submit"
type="primary"
onClick={this.handleOk}
>
Create
</Button>,
]}
>
<Form {...layout} ref={this.formRef} name="control-ref" labelAlign="left">
<Form.Item
name="Name"
label="Name"
rules={[
{
required: true,
message: 'Please input name!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,60}$', 'g'),
message:
'The maximum length is 60,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="Address"
label="URL"
rules={[
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
{
required: true,
message: 'Please input URL!',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table dataSource={capabilityList} pagination={false} rowKey={(record) => record.name}>
<Column
title="Name"
dataIndex="name"
key="name"
render={(text, record) => {
return (
<Link to={{ pathname: '/Capability/Detail', state: { name: record.name } }}>
{text}
</Link>
);
}}
/>
<Column
title="URL"
dataIndex="url"
key="url"
width="60%"
render={(text) => {
return (
<div className="hoverItem">
<a href={text} target="_blank" rel="noreferrer">
{text}
</a>
<div className="copyIcon" onClick={() => this.copyURL(text)}>
<CopyOutlined />
</div>
</div>
);
}}
/>
<Column
title="Operations"
dataIndex="name"
key="name"
render={(text, record, index) => {
return (
<Space>
<Button
loading={record.btnSyncLoading}
onClick={() => this.syncSignle(text, index)}
type="primary"
ghost
>
sync
</Button>
<Button onClick={() => this.showDeleteConfirm(text)}>remove</Button>
</Space>
);
}}
/>
</Table>
</Spin>
</PageContainer>
</div>
);
}
}

View File

@@ -1,3 +1,14 @@
p {
margin: 0;
}
.hoverItem {
.copyIcon {
display: none;
margin-left: 10px;
font-size: 16px;
cursor: pointer;
}
}
.hoverItem:hover .copyIcon {
display: inline-block;
}

View File

@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Table, Space, Modal, Form, Input, Tooltip } from 'antd';
import { Button, Table, Space, Modal, Form, Input, Tooltip, Breadcrumb } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import './index.less';
import { connect } from 'dva';
import { Link } from 'umi';
import * as _ from 'lodash';
const { confirm } = Modal;
@@ -147,64 +148,76 @@ const TableList = (props) => {
},
];
return (
<PageContainer>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={showModal}>
Create
</Button>
</Space>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>System</Breadcrumb.Item>
<Breadcrumb.Item>Env</Breadcrumb.Item>
</Breadcrumb>
</div>
<Modal
getContainer={false}
title={env && env.envName ? 'Update Env' : 'Create Env'}
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="submit" type="primary" onClick={handleOk}>
{env && env.envName ? 'Update' : 'Create'}
</Button>,
]}
>
<Form {...layout} form={form} name="control-ref" labelAlign="left">
<Form.Item
name="envName"
label="Env"
rules={[
{
required: true,
message: 'Please input Evn!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message: 'The maximum length is 63,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input disabled={!!(env && env.envName)} />
</Form.Item>
<Form.Item
name="namespace"
label="Namespace"
rules={[
{
required: true,
message: 'Please specify a Namespace!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message:
'The maximum length is 63,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table rowKey={(record) => record.envName} columns={columns} dataSource={tableEnvs} />
</PageContainer>
<PageContainer>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={showModal}>
Create
</Button>
</Space>
</div>
<Modal
getContainer={false}
title={env && env.envName ? 'Update Env' : 'Create Env'}
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="submit" type="primary" onClick={handleOk}>
{env && env.envName ? 'Update' : 'Create'}
</Button>,
]}
>
<Form {...layout} form={form} name="control-ref" labelAlign="left">
<Form.Item
name="envName"
label="Env"
rules={[
{
required: true,
message: 'Please input Evn!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message:
'The maximum length is 63,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input disabled={!!(env && env.envName)} />
</Form.Item>
<Form.Item
name="namespace"
label="Namespace"
rules={[
{
required: true,
message: 'Please specify a Namespace!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message:
'The maximum length is 63,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table rowKey={(record) => record.envName} columns={columns} dataSource={tableEnvs} />
</PageContainer>
</div>
);
};
export default connect((env) => {

View File

@@ -1,9 +1,11 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Form, Input, Button, Row, Col, Tabs, Table } from 'antd';
import { Form, Input, Button, Row, Col, Tabs, Table, Breadcrumb, Space } from 'antd';
import { CheckCircleOutlined } from '@ant-design/icons';
import _ from 'lodash';
import { connect } from 'dva';
import { Link } from 'umi';
const { TabPane } = Tabs;
const layout = {
@@ -27,7 +29,12 @@ const columns = [
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (text) => <a>{text}</a>,
render: (text) => (
<div>
<CheckCircleOutlined style={{ fontSize: '20px', color: '#4CAF51' }} />
<a style={{ marginLeft: '6px' }}>{text}</a>
</div>
),
},
{
title: 'Ready',
@@ -54,6 +61,17 @@ const columns = [
dataIndex: 'Age',
key: 'Age',
},
{
title: 'Action',
dataIndex: 'name',
key: 'name',
render: () => (
<Space>
<Button>logs</Button>
<Button>exec</Button>
</Space>
),
},
];
const data = [
@@ -120,13 +138,6 @@ const data1 = [
LastTransition: '2d',
},
];
// const demoText = `H4sIAAAAAAAA/6xUwW7bOhD8lYc9U4lsWfazgB4C5Na0DZK0lyKHNbmyWVMkQ66UGob/vSBdt3GCxEXRmwjujmZnZrmFtbYKGrgkb9ymI8sgAL3+QiFqZ6EB9D6eDyMQ0BGjQkZotmCxI2hAOmcKDBtcrJCxGMrWfn+Ygsj30aNMRYpa7E0CloGQtbN3uqPI2HlobG+MAIMLMjEBo/cvcEGAW3wjyZH4LGh3JpHZ0Jl25yuMK2hgPkPV1lRJbKfjWVlNpqps69mkrNu2qv5X5UzV9ULVIOC4P1IYtHxzlOGXFEMJOwForeM8Rib8GjOdZD3Avz6Ae7QUiuWwhuYZtWEk/nuvrXp3+4cgpzw53f3SsePKjrLaHHpKSuTGG2opkJUUofm6Pc7O84FAHPL2e6ZTrPssZDWq5+O6GhVqPquLybyaF9gqKsbVTMpyXFZ1m8yVznJwxlCAJrEUsDBOrj8lopdkiDOvFk2k3f1OQPQkk4mRDEl2IX13yHJ1dSqQx6nYCWDqvEGmDPFkU/4+8/86qUbbNQWVw2lTFKABsrgwpN52+olQSWDUlsLe7VPm6Q6XlPdABspPS1imTihWcC8gUHR9yNHZQqCHniLnb+l7aKAuu/zsdC5soIHp5IPOZDLqdW/MtTNapqsL84ibCLt7cVi5Cyldb/njCYLYs+tS4e1R251bkz1EaK/Rz4IrbdfxEKEkDAdkWm4Sa9749LMbZ4y2y89epTgICEfnZrvb9yH3MZ9+BAAA//+bxjCThQUAAA
// objectset.rio.cattle.io/id service
// objectset.rio.cattle.io/owner-gvk rio.cattle.io/v1, Kind=Service
// objectset.rio.cattle.io/owner-name cool-aryabhata-v0fnxq6
// objectset.rio.cattle.io/owner-namespace default
// rio.cattle.io/mesh true
// Controlled By cool-aryabhata-v0fnxq6`;
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.applist,
@@ -144,6 +155,7 @@ class TableList extends React.Component {
hasShowEdit2: false,
traitItem: {},
appName: '',
envName: '',
appDetailData: {},
};
}
@@ -153,16 +165,27 @@ class TableList extends React.Component {
}
async getInitialData() {
const traitItem = _.get(this.props, 'location.state.traitItem', {});
let traitItem = '';
let appName = '';
let envName = '';
if (this.props.location.state) {
traitItem = _.get(this.props, 'location.state.traitItem', '');
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('traitItem', traitItem);
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
traitItem = sessionStorage.getItem('traitItem');
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
traitItem,
appName,
envName,
});
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
if (appName && envName) {
this.setState({
appName,
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
@@ -203,17 +226,7 @@ class TableList extends React.Component {
};
render() {
const { hasShowEdit, hasShowEdit2 } = this.state;
// let finallyText;
// if (demoText && demoText.length > 50) {
// finallyText = (
// <Tooltip placement="topRight" title={demoText}>
// <Button>{`${demoText.substring(0, 50)}......`}</Button>
// </Tooltip>
// );
// } else {
// finallyText = <p>{demoText}</p>;
// }
const { hasShowEdit, hasShowEdit2, envName } = this.state;
const status = _.get(this.state.appDetailData, 'Status', '');
const { traitItem, appName } = this.state;
const metadata = _.get(traitItem, 'metadata', '');
@@ -225,340 +238,267 @@ class TableList extends React.Component {
traitType = 2;
}
return (
<PageContainer>
<div className="card-container trait-detail">
<h2>{traitName}</h2>
<p style={{ marginBottom: '20px' }}>
<i>
{traitItem.apiVersion}1,Name={appName}
</i>
</p>
<Tabs>
<TabPane tab="Summary" key="1">
<div>
<Row>
<Col span="12">
<div className="hasBorder">
<div
className="hasPadding"
style={{ display: !hasShowEdit ? 'block' : 'none' }}
>
<p className="title">Configuration</p>
<Row>
{traitType === 2 ? (
<Fragment>
<Col span="8">
<p>domain</p>
</Col>
<Col span="16">
<p>{_.get(spec, 'rules[0].host', '')}</p>
</Col>
<Col span="8">
<p>service</p>
</Col>
<Col span="16">
<p>
{_.get(spec, 'rules[0].http.paths[0].backend.serviceName', '')}
</p>
</Col>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>
{_.get(spec, 'rules[0].http.paths[0].backend.servicePort', '')}
</p>
</Col>
</Fragment>
) : (
Object.keys(spec).map((currentKey) => {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
</Col>
</Fragment>
);
})
)}
{/* {Object.keys(spec).map((currentKey) => {
return (
<Fragment key={currentKey}>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link
to={{
pathname: '/ApplicationList/ApplicationListDetail',
state: { appName, envName },
}}
>
ApplicationListDetail
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>TraitDetail</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<div className="card-container trait-detail">
<h2>{traitName}</h2>
<p style={{ marginBottom: '20px' }}>
<i>
{traitItem.apiVersion}1,Name={appName}
</i>
</p>
<Tabs>
<TabPane tab="Summary" key="1">
<div>
<Row>
<Col span="12">
<div className="hasBorder">
<div
className="hasPadding"
style={{ display: !hasShowEdit ? 'block' : 'none' }}
>
<p className="title">Configuration</p>
<Row>
{traitType === 2 ? (
<Fragment>
<Col span="8">
<p>{currentKey}</p>
<p>domain</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
<p>{_.get(spec, 'rules[0].host', '')}</p>
</Col>
<Col span="8">
<p>service</p>
</Col>
<Col span="16">
<p>
{_.get(spec, 'rules[0].http.paths[0].backend.serviceName', '')}
</p>
</Col>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>
{_.get(spec, 'rules[0].http.paths[0].backend.servicePort', '')}
</p>
</Col>
</Fragment>
);
})} */}
</Row>
{/* <Row>
<Col span="10">
<div style={{ color: 'black' }}>
<p>Deployment Strategy</p>
<p>Rolling Update Strategy</p>
<p>Selectors</p>
<p>Min Ready Seconds</p>
<p>Revision History Limit</p>
<p>Replicas</p>
</div>
</Col>
<Col>
<p>RollingUpdate</p>
<p>Max Surge 25%, Max Unavailable 25%</p>
<p>
<Tag color="orange">aryabhataapp:cool</Tag>
<Tag color="orange">version:v0</Tag>
</p>
<p>0</p>
<p>10</p>
<p>1</p>
</Col>
</Row> */}
</div>
<div
className="hasPadding"
style={{ display: hasShowEdit ? 'block' : 'none' }}
>
<p className="title">Deployment Editor</p>
<Form
labelAlign="left"
{...layout}
ef={this.formRefStep1}
name="control-ref"
onFinish={this.onFinishStep1}
>
<div className="relativeBox">
<Form.Item name="Replicas" label="Replicas">
<Input type="number" />
</Form.Item>
</div>
<div style={{ marginBottom: '16px' }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button style={{ marginLeft: '16px' }} onClick={this.changeShowEdit}>
Cancle
</Button>
</div>
</Form>
</div>
<div style={{ display: !hasShowEdit ? 'block' : 'none' }}>
) : (
Object.keys(spec).map((currentKey) => {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
</Col>
</Fragment>
);
})
)}
</Row>
</div>
<div
style={{ width: '100%', borderTop: '1px solid #eee', height: '0px' }}
/>
<div>
<Button
className="textAlignLeft"
type="link"
block
onClick={this.changeShowEdit}
className="hasPadding"
style={{ display: hasShowEdit ? 'block' : 'none' }}
>
<p className="title">Deployment Editor</p>
<Form
labelAlign="left"
{...layout}
ef={this.formRefStep1}
name="control-ref"
onFinish={this.onFinishStep1}
>
Edit
<div className="relativeBox">
<Form.Item name="Replicas" label="Replicas">
<Input type="number" />
</Form.Item>
</div>
<div style={{ marginBottom: '16px' }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button style={{ marginLeft: '16px' }} onClick={this.changeShowEdit}>
Cancle
</Button>
</div>
</Form>
</div>
<div style={{ display: !hasShowEdit ? 'block' : 'none' }}>
<div
style={{ width: '100%', borderTop: '1px solid #eee', height: '0px' }}
/>
<div>
<Button
className="textAlignLeft"
type="link"
block
onClick={this.changeShowEdit}
>
Edit
</Button>
</div>
</div>
</div>
</Col>
<Col span="1" />
<Col span="10">
<div className="hasBorder">
<div className="hasPadding">
<p className="title">Status</p>
<p>{status}</p>
</div>
</div>
</Col>
</Row>
<p className="title hasBG">Pods</p>
<Table columns={columns} dataSource={data} pagination={false} />
<p className="title hasBG">Conditions</p>
<Table columns={columns1} dataSource={data1} pagination={false} />
<p className="title hasBG">Pod Template</p>
<div className="hasBorder">
<div
className="hasPadding"
style={{ display: !hasShowEdit2 ? 'block' : 'none' }}
>
<p className="title">Container cool-aryabhata-v0fnxq6</p>
<Row>
<Col span="2">
<div style={{ color: 'black' }}>
<p>Image</p>
<p>Args</p>
</div>
</Col>
<Col>
<p>secret</p>
<p>[&apos;-h&apos;]</p>
</Col>
</Row>
</div>
<div
className="hasPadding"
style={{ display: hasShowEdit2 ? 'block' : 'none' }}
>
<p className="title">Deployment Editor</p>
<Form
style={{ width: '50%' }}
{...layout1}
labelAlign="left"
ef={this.formRefStep2}
name="control-ref"
onFinish={this.onFinishStep2}
>
<div className="relativeBox">
<Form.Item name="Image" label="Image">
<Input />
</Form.Item>
</div>
<div style={{ marginBottom: '16px' }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button style={{ marginLeft: '16px' }} onClick={this.changeShowEdit2}>
Cancle
</Button>
</div>
</div>
</Form>
</div>
</Col>
<Col span="1" />
<Col span="10">
<div className="hasBorder">
<div className="hasPadding">
<p className="title">Status</p>
<p>{status}</p>
{/* <Row>
<Col span="10">
<div style={{ color: 'black' }}>
<p>Avaliable Replicas</p>
<p>Ready Replicas</p>
<p>Total Replicas</p>
<p>Unavaliable Replicas</p>
<p>Updated Replicas</p>
</div>
</Col>
<Col>
<p>0</p>
<p>0</p>
<p>1</p>
<p>1</p>
<p>1</p>
</Col>
</Row> */}
</div>
</div>
</Col>
</Row>
<p className="title" style={{ marginTop: '16px' }}>
Pods
</p>
<Table columns={columns} dataSource={data} />
<p className="title">Conditions</p>
<Table columns={columns1} dataSource={data1} />
<p className="title">Pod Template</p>
<div className="hasBorder">
<div className="hasPadding" style={{ display: !hasShowEdit2 ? 'block' : 'none' }}>
<p className="title">Container cool-aryabhata-v0fnxq6</p>
<Row>
<Col span="2">
<div style={{ color: 'black' }}>
<p>Image</p>
<p>Args</p>
</div>
</Col>
<Col>
<p>secret</p>
<p>[&apos;-h&apos;]</p>
</Col>
</Row>
</div>
<div className="hasPadding" style={{ display: hasShowEdit2 ? 'block' : 'none' }}>
<p className="title">Deployment Editor</p>
<Form
style={{ width: '50%' }}
{...layout1}
labelAlign="left"
ef={this.formRefStep2}
name="control-ref"
onFinish={this.onFinishStep2}
>
<div className="relativeBox">
<Form.Item name="Image" label="Image">
<Input />
</Form.Item>
</div>
<div style={{ marginBottom: '16px' }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button style={{ marginLeft: '16px' }} onClick={this.changeShowEdit2}>
Cancle
<div style={{ display: !hasShowEdit2 ? 'block' : 'none' }}>
<div style={{ width: '100%', borderTop: '1px solid #eee', height: '0px' }} />
<div>
<Button
className="textAlignLeft"
type="link"
block
onClick={this.changeShowEdit2}
>
Edit
</Button>
</div>
</Form>
</div>
<div style={{ display: !hasShowEdit2 ? 'block' : 'none' }}>
<div style={{ width: '100%', borderTop: '1px solid #eee', height: '0px' }} />
<div>
<Button
className="textAlignLeft"
type="link"
block
onClick={this.changeShowEdit2}
>
Edit
</Button>
</div>
</div>
</div>
</div>
</TabPane>
<TabPane tab="Metadata" key="2">
<div className="hasBorder">
<div className="hasPadding">
<p className="title">Metadata</p>
{Object.keys(metadata).map((currentKey8) => {
if (currentKey8 === 'annotations') {
</TabPane>
<TabPane tab="Metadata" key="2">
<div className="hasBorder">
<div className="hasPadding">
<p className="title">Metadata</p>
{Object.keys(metadata).map((currentKey8) => {
if (currentKey8 === 'annotations') {
return (
<Row key={currentKey8}>
<Col span="4">
<div style={{ color: 'black' }}>
<p>{currentKey8}</p>
</div>
</Col>
<Col span="20">
{Object.keys(metadata[currentKey8]).map((currentKey9) => {
return (
<Row key={currentKey9}>
<Col span="8">
<div style={{ color: 'black' }}>
<p>{currentKey9}</p>
</div>
</Col>
<Col>
<p>{metadata[currentKey8][currentKey9]}</p>
</Col>
</Row>
);
})}
</Col>
</Row>
);
}
return (
<Row key={currentKey8}>
<Col span="4">
<div style={{ color: 'black' }}>
<p>{currentKey8}</p>
</div>
<p>{currentKey8}</p>
</Col>
<Col span="20">
{Object.keys(metadata[currentKey8]).map((currentKey9) => {
return (
<Row key={currentKey9}>
<Col span="8">
<div style={{ color: 'black' }}>
<p>{currentKey9}</p>
</div>
</Col>
<Col>
<p>{metadata[currentKey8][currentKey9]}</p>
</Col>
</Row>
);
})}
<Col>
<p>{metadata[currentKey8]}</p>
</Col>
</Row>
);
}
return (
<Row key={currentKey8}>
<Col span="4">
<p>{currentKey8}</p>
</Col>
<Col>
<p>{metadata[currentKey8]}</p>
</Col>
</Row>
);
})}
{/* <Row>
<Col span="4">
<div style={{ color: 'black' }}>
<p>Annotations</p>
</div>
</Col>
<Col span="20">
<Row>
<Col span="8">
<div style={{ color: 'black' }}>
<p>deployment.kubernetes.io/revision</p>
</div>
</Col>
<Col>
<p>1</p>
</Col>
</Row>
<Row>
<Col span="8">
<div style={{ color: 'black' }}>
<p>objectset.rio.cattle.io/applied</p>
</div>
</Col>
<Col span="16">{finallyText}</Col>
</Row>
<Row>
<Col span="8">
<div style={{ color: 'black' }}>
<p>objectset.rio.cattle.io/id</p>
</div>
</Col>
<Col span="16">
<p>service</p>
</Col>
</Row>
</Col>
</Row>
<Row>
<Col span="4">
<div style={{ color: 'black' }}>
<p>Controlled By</p>
</div>
</Col>
<Col>
<p>cool-aryabhata-v0fnxq6</p>
</Col>
</Row> */}
})}
</div>
</div>
</div>
</TabPane>
<TabPane tab="Resource Viewer" key="3">
<p>Resource Viewer</p>
</TabPane>
<TabPane tab="YAML" key="4">
<p>YAML</p>
</TabPane>
</Tabs>
</div>
</PageContainer>
</TabPane>
<TabPane tab="Resource Viewer" key="3">
<p>Resource Viewer</p>
</TabPane>
<TabPane tab="YAML" key="4">
<p>YAML</p>
</TabPane>
</Tabs>
</div>
</PageContainer>
</div>
);
}
}

View File

@@ -9,13 +9,19 @@
.hasBorder {
border: 1px solid #eee;
.hasPadding {
padding: 16px 16px 0;
padding: 16px;
}
}
.title {
color: black;
font-size: 18px;
}
.hasBG {
margin-top: 50px;
margin-bottom: 8px;
padding-left: 16px;
background: rgba(0, 0, 0, 0.04);
}
.textAlignLeft {
padding-left: 16px;
text-align: left;

View File

@@ -1,8 +1,10 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Form, Input, Button, Row, Col, Tabs, Table, Spin } from 'antd';
import { Form, Input, Button, Row, Col, Tabs, Table, Spin, Breadcrumb, Space } from 'antd';
import { CheckCircleOutlined } from '@ant-design/icons';
import { connect } from 'dva';
import { Link } from 'umi';
import _ from 'lodash';
const { TabPane } = Tabs;
@@ -27,7 +29,12 @@ const columns = [
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (text) => <a>{text}</a>,
render: (text) => (
<div>
<CheckCircleOutlined style={{ fontSize: '20px', color: '#4CAF51' }} />
<a style={{ marginLeft: '6px' }}>{text}</a>
</div>
),
},
{
title: 'Ready',
@@ -54,6 +61,17 @@ const columns = [
dataIndex: 'Age',
key: 'Age',
},
{
title: 'Action',
dataIndex: 'name',
key: 'name',
render: () => (
<Space>
<Button>logs</Button>
<Button>exec</Button>
</Space>
),
},
];
const data = [
@@ -120,13 +138,6 @@ const data1 = [
LastTransition: '2d',
},
];
// const demoText = `H4sIAAAAAAAA/6xUwW7bOhD8lYc9U4lsWfazgB4C5Na0DZK0lyKHNbmyWVMkQ66UGob/vSBdt3GCxEXRmwjujmZnZrmFtbYKGrgkb9ymI8sgAL3+QiFqZ6EB9D6eDyMQ0BGjQkZotmCxI2hAOmcKDBtcrJCxGMrWfn+Ygsj30aNMRYpa7E0CloGQtbN3uqPI2HlobG+MAIMLMjEBo/cvcEGAW3wjyZH4LGh3JpHZ0Jl25yuMK2hgPkPV1lRJbKfjWVlNpqps69mkrNu2qv5X5UzV9ULVIOC4P1IYtHxzlOGXFEMJOwForeM8Rib8GjOdZD3Avz6Ae7QUiuWwhuYZtWEk/nuvrXp3+4cgpzw53f3SsePKjrLaHHpKSuTGG2opkJUUofm6Pc7O84FAHPL2e6ZTrPssZDWq5+O6GhVqPquLybyaF9gqKsbVTMpyXFZ1m8yVznJwxlCAJrEUsDBOrj8lopdkiDOvFk2k3f1OQPQkk4mRDEl2IX13yHJ1dSqQx6nYCWDqvEGmDPFkU/4+8/86qUbbNQWVw2lTFKABsrgwpN52+olQSWDUlsLe7VPm6Q6XlPdABspPS1imTihWcC8gUHR9yNHZQqCHniLnb+l7aKAuu/zsdC5soIHp5IPOZDLqdW/MtTNapqsL84ibCLt7cVi5Cyldb/njCYLYs+tS4e1R251bkz1EaK/Rz4IrbdfxEKEkDAdkWm4Sa9749LMbZ4y2y89epTgICEfnZrvb9yH3MZ9+BAAA//+bxjCThQUAAA
// objectset.rio.cattle.io/id service
// objectset.rio.cattle.io/owner-gvk rio.cattle.io/v1, Kind=Service
// objectset.rio.cattle.io/owner-name cool-aryabhata-v0fnxq6
// objectset.rio.cattle.io/owner-namespace default
// rio.cattle.io/mesh true
// Controlled By cool-aryabhata-v0fnxq6`;
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.applist,
@@ -143,6 +154,8 @@ class TableList extends React.PureComponent {
hasShowEdit: false,
hasShowEdit2: false,
appDetailData: {},
appName: '',
envName: '',
};
}
@@ -151,8 +164,21 @@ class TableList extends React.PureComponent {
}
async getInitialData() {
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
let appName = '';
let envName = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
appName,
envName,
});
if (appName && envName) {
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
@@ -194,338 +220,246 @@ class TableList extends React.PureComponent {
};
render() {
const { hasShowEdit, hasShowEdit2 } = this.state;
// let finallyText;
// if (demoText && demoText.length > 50) {
// finallyText = (
// <Tooltip placement="topRight" title={demoText}>
// <Button>{`${demoText.substring(0, 50)}......`}</Button>
// </Tooltip>
// );
// } else {
// finallyText = <p>{demoText}</p>;
// }
const { hasShowEdit, hasShowEdit2, appName, envName } = this.state;
const status = _.get(this.state.appDetailData, 'Status', '');
const Workload = _.get(this.state.appDetailData, 'Workload.workload', {});
const metadata = _.get(Workload, 'metadata', {});
let containers = {};
// if (Workload.kind === 'ContainerizedWorkload') {
// containers = _.get(Workload, 'spec.containers[0]', {});
// } else if (Workload.kind === 'Deployment') {
// containers = _.get(Workload, 'spec.template.spec.containers[0]', {});
// }
containers = _.get(Workload, 'spec.containers[0]', {});
let { loadingAll } = this.props;
loadingAll = loadingAll || false;
return (
<PageContainer>
<Spin spinning={loadingAll}>
<div className="card-container workload-detail">
<h2>{Workload.kind}</h2>
<p style={{ marginBottom: '20px' }}>
<i>
{Workload.apiVersion},Name={_.get(Workload, 'metadata.name', '')}
</i>
</p>
<Tabs>
<TabPane tab="Summary" key="1">
<div>
<Row>
<Col span="12">
<div className="hasBorder">
<div
className="hasPadding"
style={{ display: !hasShowEdit ? 'block' : 'none' }}
>
<p className="title">Configuration</p>
<Row>
{Object.keys(containers).map((currentKey) => {
if (currentKey === 'ports') {
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link
to={{
pathname: '/ApplicationList/ApplicationListDetail',
state: { appName, envName },
}}
>
ApplicationListDetail
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>WorkloadDetail</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<Spin spinning={loadingAll}>
<div className="card-container workload-detail">
<h2>{Workload.kind}</h2>
<p style={{ marginBottom: '20px' }}>
<i>
{Workload.apiVersion},Name={_.get(Workload, 'metadata.name', '')}
</i>
</p>
<Tabs>
<TabPane tab="Summary" key="1">
<div>
<Row>
<Col span="12">
<div className="hasBorder">
<div
className="hasPadding"
style={{ display: !hasShowEdit ? 'block' : 'none' }}
>
<p className="title">Configuration</p>
<Row>
{Object.keys(containers).map((currentKey) => {
if (currentKey === 'ports') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>
{_.get(containers[currentKey], '[0].containerPort', '')}
</p>
</Col>
</Fragment>
);
// eslint-disable-next-line no-else-return
} else if (currentKey === 'name') {
return <Fragment key={currentKey} />;
}
return (
<Fragment key={currentKey}>
<Col span="8">
<p>port</p>
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>
{_.get(containers[currentKey], '[0].containerPort', '')}
</p>
<p>{containers[currentKey]}</p>
</Col>
</Fragment>
);
// eslint-disable-next-line no-else-return
} else if (currentKey === 'name') {
return <Fragment key={currentKey} />;
}
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{containers[currentKey]}</p>
</Col>
</Fragment>
);
})}
</Row>
{/* <Row>
<Col span="10">
<div style={{ color: 'black' }}>
<p>Deployment Strategy</p>
<p>Rolling Update Strategy</p>
<p>Selectors</p>
<p>Min Ready Seconds</p>
<p>Revision History Limit</p>
<p>Replicas</p>
</div>
</Col>
<Col>
<p>RollingUpdate</p>
<p>Max Surge 25%, Max Unavailable 25%</p>
<p>
<Tag color="orange">aryabhataapp:cool</Tag>
<Tag color="orange">version:v0</Tag>
</p>
<p>0</p>
<p>10</p>
<p>1</p>
</Col>
</Row> */}
</div>
<div
className="hasPadding"
style={{ display: hasShowEdit ? 'block' : 'none' }}
>
<p className="title">Deployment Editor</p>
<Form
labelAlign="left"
{...layout}
ef={this.formRefStep1}
name="control-ref"
onFinish={this.onFinishStep1}
>
<div className="relativeBox">
<Form.Item name="Replicas" label="Replicas">
<Input type="number" />
</Form.Item>
</div>
<div style={{ marginBottom: '16px' }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button style={{ marginLeft: '16px' }} onClick={this.changeShowEdit}>
Cancle
</Button>
</div>
</Form>
</div>
<div style={{ display: !hasShowEdit ? 'block' : 'none' }}>
})}
</Row>
</div>
<div
style={{ width: '100%', borderTop: '1px solid #eee', height: '0px' }}
/>
<div>
<Button
className="textAlignLeft"
type="link"
block
onClick={this.changeShowEdit}
className="hasPadding"
style={{ display: hasShowEdit ? 'block' : 'none' }}
>
<p className="title">Deployment Editor</p>
<Form
labelAlign="left"
{...layout}
ef={this.formRefStep1}
name="control-ref"
onFinish={this.onFinishStep1}
>
Edit
<div className="relativeBox">
<Form.Item name="Replicas" label="Replicas">
<Input type="number" />
</Form.Item>
</div>
<div style={{ marginBottom: '16px' }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button
style={{ marginLeft: '16px' }}
onClick={this.changeShowEdit}
>
Cancle
</Button>
</div>
</Form>
</div>
<div style={{ display: !hasShowEdit ? 'block' : 'none' }}>
<div
style={{ width: '100%', borderTop: '1px solid #eee', height: '0px' }}
/>
<div>
<Button
className="textAlignLeft"
type="link"
block
onClick={this.changeShowEdit}
>
Edit
</Button>
</div>
</div>
</div>
</Col>
<Col span="1" />
<Col span="10">
<div className="hasBorder">
<div className="hasPadding">
<p className="title">Status</p>
<p>{status}</p>
</div>
</div>
</Col>
</Row>
<p className="title hasBG">Pods</p>
<Table columns={columns} dataSource={data} pagination={false} />
<p className="title hasBG">Conditions</p>
<Table columns={columns1} dataSource={data1} pagination={false} />
<p className="title hasBG">Pod Template</p>
<div className="hasBorder">
<div
className="hasPadding"
style={{ display: !hasShowEdit2 ? 'block' : 'none' }}
>
<p className="title">Container cool-aryabhata-v0fnxq6</p>
<Row>
<Col span="2">
<div style={{ color: 'black' }}>
<p>Image</p>
<p>Args</p>
</div>
</Col>
<Col>
<p>secret</p>
<p>[&apos;-h&apos;]</p>
</Col>
</Row>
</div>
<div
className="hasPadding"
style={{ display: hasShowEdit2 ? 'block' : 'none' }}
>
<p className="title">Deployment Editor</p>
<Form
style={{ width: '50%' }}
{...layout1}
labelAlign="left"
ef={this.formRefStep2}
name="control-ref"
onFinish={this.onFinishStep2}
>
<div className="relativeBox">
<Form.Item name="Image" label="Image">
<Input />
</Form.Item>
</div>
<div style={{ marginBottom: '16px' }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button style={{ marginLeft: '16px' }} onClick={this.changeShowEdit2}>
Cancle
</Button>
</div>
</Form>
</div>
<div style={{ display: !hasShowEdit2 ? 'block' : 'none' }}>
<div
style={{ width: '100%', borderTop: '1px solid #eee', height: '0px' }}
/>
<div>
<Button
className="textAlignLeft"
type="link"
block
onClick={this.changeShowEdit2}
>
Edit
</Button>
</div>
</div>
</Col>
<Col span="1" />
<Col span="10">
<div className="hasBorder">
<div className="hasPadding">
<p className="title">Status</p>
<p>{status}</p>
{/* <Row>
<Col span="10">
<div style={{ color: 'black' }}>
<p>Avaliable Replicas</p>
<p>Ready Replicas</p>
<p>Total Replicas</p>
<p>Unavaliable Replicas</p>
<p>Updated Replicas</p>
</div>
</div>
</div>
</TabPane>
<TabPane tab="Metadata" key="2">
<div className="hasBorder">
<div className="hasPadding">
<p className="title">Metadata</p>
{Object.keys(metadata).map((currentKey6) => {
return (
<Row key={currentKey6}>
<Col span="4">
<p>{currentKey6}</p>
</Col>
<Col>
<p>0</p>
<p>0</p>
<p>1</p>
<p>1</p>
<p>1</p>
<p>{metadata[currentKey6]}</p>
</Col>
</Row> */}
</div>
</div>
</Col>
</Row>
<p className="title" style={{ marginTop: '16px' }}>
Pods
</p>
<Table columns={columns} dataSource={data} />
<p className="title">Conditions</p>
<Table columns={columns1} dataSource={data1} />
<p className="title">Pod Template</p>
<div className="hasBorder">
<div
className="hasPadding"
style={{ display: !hasShowEdit2 ? 'block' : 'none' }}
>
<p className="title">Container cool-aryabhata-v0fnxq6</p>
<Row>
<Col span="2">
<div style={{ color: 'black' }}>
<p>Image</p>
<p>Args</p>
</div>
</Col>
<Col>
<p>secret</p>
<p>[&apos;-h&apos;]</p>
</Col>
</Row>
</div>
<div
className="hasPadding"
style={{ display: hasShowEdit2 ? 'block' : 'none' }}
>
<p className="title">Deployment Editor</p>
<Form
style={{ width: '50%' }}
{...layout1}
labelAlign="left"
ef={this.formRefStep2}
name="control-ref"
onFinish={this.onFinishStep2}
>
<div className="relativeBox">
<Form.Item name="Image" label="Image">
<Input />
</Form.Item>
</div>
<div style={{ marginBottom: '16px' }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button style={{ marginLeft: '16px' }} onClick={this.changeShowEdit2}>
Cancle
</Button>
</div>
</Form>
</div>
<div style={{ display: !hasShowEdit2 ? 'block' : 'none' }}>
<div style={{ width: '100%', borderTop: '1px solid #eee', height: '0px' }} />
<div>
<Button
className="textAlignLeft"
type="link"
block
onClick={this.changeShowEdit2}
>
Edit
</Button>
</div>
</Row>
);
})}
</div>
</div>
</div>
</TabPane>
<TabPane tab="Metadata" key="2">
<div className="hasBorder">
<div className="hasPadding">
<p className="title">Metadata</p>
{Object.keys(metadata).map((currentKey6) => {
return (
<Row key={currentKey6}>
<Col span="4">
<p>{currentKey6}</p>
</Col>
<Col>
<p>{metadata[currentKey6]}</p>
</Col>
</Row>
);
})}
{/* <Row>
<Col span="4">
<div style={{ color: 'black' }}>
<p>Age</p>
<p>Labels</p>
</div>
</Col>
<Col>
<p>2d</p>
<p>
<Tag color="orange">aryabhataapp:cool</Tag>
<Tag color="orange">version:v0</Tag>
</p>
</Col>
</Row>
<Row>
<Col span="4">
<div style={{ color: 'black' }}>
<p>Annotations</p>
</div>
</Col>
<Col span="20">
<Row>
<Col span="8">
<div style={{ color: 'black' }}>
<p>deployment.kubernetes.io/revision</p>
</div>
</Col>
<Col>
<p>1</p>
</Col>
</Row>
<Row>
<Col span="8">
<div style={{ color: 'black' }}>
<p>objectset.rio.cattle.io/applied</p>
</div>
</Col>
<Col span="16">{finallyText}</Col>
</Row>
<Row>
<Col span="8">
<div style={{ color: 'black' }}>
<p>objectset.rio.cattle.io/id</p>
</div>
</Col>
<Col span="16">
<p>service</p>
</Col>
</Row>
</Col>
</Row>
<Row>
<Col span="4">
<div style={{ color: 'black' }}>
<p>Controlled By</p>
</div>
</Col>
<Col>
<p>cool-aryabhata-v0fnxq6</p>
</Col>
</Row> */}
</div>
</div>
</TabPane>
<TabPane tab="Resource Viewer" key="3">
<p>Resource Viewer</p>
</TabPane>
<TabPane tab="YAML" key="4">
<p>YAML</p>
</TabPane>
</Tabs>
</div>
</Spin>
</PageContainer>
</TabPane>
<TabPane tab="Resource Viewer" key="3">
<p>Resource Viewer</p>
</TabPane>
<TabPane tab="YAML" key="4">
<p>YAML</p>
</TabPane>
</Tabs>
</div>
</Spin>
</PageContainer>
</div>
);
}
}

View File

@@ -9,13 +9,19 @@
.hasBorder {
border: 1px solid #eee;
.hasPadding {
padding: 16px 16px 0;
padding: 16px;
}
}
.title {
color: black;
font-size: 18px;
}
.hasBG {
margin-top: 50px;
margin-bottom: 8px;
padding-left: 16px;
background: rgba(0, 0, 0, 0.04);
}
.textAlignLeft {
padding-left: 16px;
text-align: left;

View File

@@ -11,7 +11,6 @@ export async function getapplist({ url }) {
export async function createApp({ params, url }) {
return request(url, {
method: 'POST',
// body:JSON.stringify(params),
data: {
...params,
},

View File

@@ -11,7 +11,6 @@ export async function getCapabilityCenterlist() {
export async function createCapabilityCenter({ params }) {
return request('/api/capability-centers/', {
method: 'put',
// body:JSON.stringify(params),
data: {
...params,
},
@@ -32,10 +31,10 @@ export async function syncCapability({ capabilityCenterName }) {
});
}
/*
* Delete /api/capabilities/:capabilityName (删除一个 capability)
* DELETE /capability-centers/:capabilityCenterName/ (Capability Center delete) (删除一个 capabilityCenter)
*/
export async function deleteCapability({ capabilityName }) {
return request(`/api/capabilities/${capabilityName}`, {
export async function deleteCapability({ capabilityCenterName }) {
return request(`/api/capability-centers/${capabilityCenterName}`, {
method: 'delete',
});
}

View File

@@ -35,11 +35,6 @@ const errorHandler = async (error) => {
} else if (!response) {
message.error('网络异常: 您的网络发生异常,无法连接服务器');
}
// throw error; // 如果throw. 错误将继续抛出.
// 如果return, 则将值作为返回. 'return;' 相当于return undefined, 在处理结果时判断response是否有值即可.
// return {some: 'data'};
// return response;
};
/**
@@ -47,9 +42,7 @@ const errorHandler = async (error) => {
*/
const request = extend({
errorHandler, // 默认错误处理
credentials: 'include', // 默认请求是否带上cookie,
// prefix: '/api/v1',
// timeout: 1000,
credentials: 'include', // 默认请求是否带上cookie
});
/*
@@ -75,6 +68,5 @@ request.interceptors.response.use(async (response) => {
}
// eslint-disable-next-line consistent-return
return data.data;
// return response;
});
export default request;

View File

@@ -1 +0,0 @@
kubevela.io

View File

@@ -1,2 +0,0 @@
theme: jekyll-theme-cayman
source: website

View File

@@ -1,25 +0,0 @@
## Overview
For developers and operators, KubeVela, as a out-of-box Cloud Native Application Management Platform, provides numerous
workloads and operation toolings for application defining, deployment, scaling, traffic, rollout, routing, monitoring,
logging, alerting, CICD and so on.
For platform builders, KubeVela, as a highly extensible PaaS/Serverless Core, provides pluginable capabilities, an elegant
way to integrate any Workloads and Traits.
![](./resource/3-minutes-demo.jpg)
## Get Started
Check out [Get Started](https://github.com/oam-dev/kubevela) to try KubeVela.
Explore [Cli docs](https://github.com/oam-dev/kubevela/tree/master/documentation/cli) to get a quick glance
of the power of KubeVela.
## Community
## Support or Contact
Having trouble with KubeVela? File an [issue](https://github.com/oam-dev/kubevela/issues), or contact us directly [Twitter](https://twitter.com/oam_dev) or [Gitter](https://gitter.im/oam-dev/)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

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