├── .babelrc
├── .dockerignore
├── .gitattributes
├── .github
└── workflows
│ ├── lint.yml
│ ├── node.js.yml
│ └── rebase.yml
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE.txt
├── README.md
├── __mocks__
├── 401.json
├── 403.json
├── 404.json
├── 409.json
├── 500.json
├── advertisement.json
├── advertisement_noStatus.json
├── advertisement_notAccepted.json
├── advertisement_refused.json
├── apiextension.k8s.io.json
├── apis.json
├── apiv1.json
├── apps.json
├── config.liqo.io.json
├── configmap_clusterID.json
├── configs.json
├── configs_updated.json
├── crd_empty.json
├── crd_fetch.json
├── crd_fetch_long.json
├── dashboard.liqo.io.json
├── dashboardconf.json
├── dashboardconf_2.json
├── deployments.json
├── discovery.liqo.io.json
├── fileMock.js
├── foreigncluster.json
├── foreigncluster_new.json
├── foreigncluster_noIncoming.json
├── foreigncluster_noJoin.json
├── foreigncluster_noOutgoing.json
├── graph.json
├── histocharts.json
├── histocharts_wrong.json
├── kubernetesjsonschema.json
├── liqodashtest.json
├── liqodashtest_modifiedCRD.json
├── liqodashtest_new.json
├── liqodashtest_noSpec_noStatus.json
├── liqodashtest_update.json
├── manyResources.json
├── mockGraph.js
├── namespaces.json
├── net.liqo.io.json
├── no_Ann_noRes_noSch.json
├── nodes.json
├── nodes_metrics.json
├── peeringrequest.json
├── piecharts.json
├── piecharts_wrong.json
├── pod.json
├── pod_logs.txt
├── pods.json
├── pods_metrics.json
├── scheduling.liqo.io.json
├── schedulingnodes.json
├── searchdomain.json
├── sharing.liqo.io.json
├── tunnelendpoints.json
├── views.json
├── views_alt_template.json
├── views_another.json
├── views_empty.json
├── views_modified.json
├── views_withLayout.json
└── virtualkubelet.liqo.io.json
├── babel.config.js
├── docker-entrypoint.sh
├── docs
├── README-CRD.md
└── images
│ ├── CRD-with-field-description.png
│ ├── CRD-without-field-description.png
│ ├── dash-logo.png
│ ├── dashboard-ui.png
│ ├── link-to-resource-new.png
│ └── link-to-resource.png
├── jest.config.js
├── jest.setup.js
├── kubernetes
└── dashboard_chart
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── README.md
│ ├── templates
│ ├── crd
│ │ ├── dashboard.liqo.io_dashboardconfigs.yaml
│ │ └── dashboard.liqo.io_views.yaml
│ └── liqodash_deployment.yaml
│ └── values.yaml
├── nginx.conf
├── package.json
├── src
├── app
│ ├── App.css
│ ├── App.js
│ ├── CR.css
│ ├── CRD.css
│ └── CRDList.css
├── assets
│ ├── database.png
│ ├── logo_4.png
│ └── name.png
├── common
│ ├── AppFooter.css
│ ├── AppFooter.js
│ ├── AppHeader.css
│ ├── AppHeader.js
│ ├── DraggableWrapper.js
│ ├── LoadingIndicator.js
│ ├── NamespaceSelect.js
│ ├── ResourceAutocomplete.js
│ ├── SideBar.css
│ └── SideBar.js
├── constants
│ └── index.js
├── customView
│ ├── AddCustomView.js
│ ├── CustomView.css
│ ├── CustomView.js
│ ├── CustomViewHeader.js
│ └── CustomViewUtils.js
├── editors
│ ├── CRD
│ │ ├── DesignEditorCRD.css
│ │ ├── DesignEditorCRD.js
│ │ ├── NewFromFile.js
│ │ ├── NewResource.js
│ │ ├── UpdateCR.css
│ │ └── UpdateCR.js
│ ├── Editor.js
│ └── OAPIV3FormGenerator
│ │ ├── CustomField.js
│ │ ├── CustomFieldTemplate.js
│ │ ├── CustomFieldTemplateViewer.js
│ │ ├── CustomSchemaField.js
│ │ ├── CustomWidget.js
│ │ ├── FormGenerator.js
│ │ └── ReferenceHandler.js
├── error-handles
│ ├── ErrorRedirect.css
│ └── ErrorRedirect.js
├── index.css
├── index.js
├── login
│ ├── Login.css
│ └── Login.js
├── resources
│ ├── APIGroup
│ │ └── APIGroupList.js
│ ├── APIResourceList
│ │ └── APIResourceList.js
│ ├── CRD
│ │ ├── CR.js
│ │ └── CRD.js
│ ├── common
│ │ ├── CustomIcon.js
│ │ ├── DashboardConfigUtils.js
│ │ ├── KubernetesSchemaAutocomplete.js
│ │ ├── LayoutUtils.js
│ │ ├── ResourceBreadcrumb.js
│ │ ├── ResourceUtils.js
│ │ └── buttons
│ │ │ ├── CustomViewButton.js
│ │ │ ├── FavouriteButton.css
│ │ │ ├── FavouriteButton.js
│ │ │ └── IconButton.js
│ ├── resource
│ │ ├── CustomTab.css
│ │ ├── CustomTab.js
│ │ ├── CustomTabContent.js
│ │ ├── ReferenceTab.js
│ │ ├── ResourceForm.js
│ │ ├── ResourceGeneral.js
│ │ ├── ResourceHeader.js
│ │ └── pod
│ │ │ └── Logs.js
│ └── resourceList
│ │ ├── ListHeader.js
│ │ ├── ResourceList.js
│ │ ├── ResourceListRenderer.js
│ │ └── columnContentFunction.js
├── services
│ ├── Colors.js
│ ├── SaveUtils.js
│ ├── TableUtils.js
│ ├── TimeUtils.js
│ ├── Utils.js
│ ├── api
│ │ ├── ApiInterface.js
│ │ ├── ApiManager.js
│ │ ├── Authenticator.js
│ │ ├── DefaultRoutes.js
│ │ └── __mocks__
│ │ │ ├── ApiManager.js
│ │ │ ├── Authenticator.js
│ │ │ └── DefaultRoutes.js
│ └── stringUtils.js
├── styles
│ └── variables.less
├── themes
│ ├── ColorPicker.js
│ ├── ThemeModifier.js
│ ├── ThemeUploader.js
│ ├── dark-theme.less
│ ├── dark.json
│ ├── light-theme.less
│ └── light.json
├── views
│ ├── CustomViewLoader.js
│ ├── PluginLoader.js
│ ├── configView
│ │ ├── ConfigView.css
│ │ └── ConfigView.js
│ └── liqo
│ │ ├── AvailablePeer.js
│ │ ├── ConnectedPeer.js
│ │ ├── ConnectionDetails.js
│ │ ├── Home.css
│ │ ├── Home.js
│ │ ├── HomeUtils.js
│ │ ├── LiqoHeader.js
│ │ ├── ListAvailable.js
│ │ ├── ListConnected.js
│ │ ├── PeerProperties.js
│ │ └── Status.js
└── widgets
│ ├── donut
│ └── Donut.js
│ ├── draggableLayout
│ └── DraggableLayout.js
│ ├── form
│ └── FormViewer.js
│ ├── graph
│ ├── GraphNet.css
│ └── GraphNet.js
│ ├── histogram
│ └── HistoChart.js
│ ├── line
│ └── LineChart.js
│ ├── piechart
│ └── PieChart.js
│ └── table
│ └── TableViewer.js
├── test
├── APIGroupList.test.js
├── AddCustomView.test.js
├── App.test.js
├── AppFooter.test.js
├── AppHeader.test.js
├── AvailableList.test.js
├── AvailablePeer.test.js
├── CR.test.js
├── CRD.test.js
├── ColumnCustomization.test.js
├── ConfigView.test.js
├── ConnectedList.test.js
├── ConnectedPeer.test.js
├── ConnectionDetails.test.js
├── CustomTab.test.js
├── CustomView.test.js
├── DashboardConfig.test.js
├── DesignEditorCRD.test.js
├── Donut.test.js
├── ErrorRedirect.test.js
├── FavouriteAndIcon.test.js
├── FormViewer.test.js
├── HistoChart.test.js
├── Home.test.js
├── LineChart.test.js
├── LiqoHeader.test.js
├── ListHeader.test.js
├── Login.test.js
├── NamespaceSelect.test.js
├── NewResource.test.js
├── OAPIV3.test.js
├── PeerProperties.test.js
├── PieChart.test.js
├── RTLUtils.js
├── ResourceForm.test.js
├── ResourceGeneral.test.js
├── ResourceHeader.test.js
├── ResourceList.test.js
├── SideBar.test.js
├── Status.test.js
└── UpdateCR.test.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [ "@babel/preset-env", {
4 | "modules": "auto",
5 | "targets": {
6 | "browsers": [
7 | "last 2 Chrome versions",
8 | "last 2 Firefox versions",
9 | "last 2 Safari versions",
10 | "last 2 iOS versions",
11 | "last 1 Android version",
12 | "last 1 ChromeAndroid version",
13 | "ie 11"
14 | ]}
15 | }],
16 | "@babel/preset-react"
17 | ],
18 | "plugins": [
19 | "@babel/plugin-proposal-object-rest-spread",
20 | "@babel/plugin-proposal-class-properties",
21 | [
22 | "@babel/plugin-transform-runtime",
23 | {
24 | "corejs": 2
25 | }
26 | ],
27 | "@babel/plugin-transform-react-jsx",
28 | ["import", {"libraryName": "antd", "libraryDirectory": "lib", "style": "true"} ]
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | ./node_modules
2 | ./package-lock.json
3 | ./dist
4 | ./k8s_library/node_modules
5 | ./k8s_library/package-lock.json
6 | ./k8s_library/dist
7 | ./kubernetes-templates
8 | ./Dockerfile
9 | ./README.md
10 | ./.dockerignore
11 | ./.git
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Check linting
2 | on:
3 | pull_request:
4 | types:
5 | - opened
6 | - reopened
7 | - synchronize
8 |
9 | jobs:
10 | frontend-lint:
11 | name: Lint the frontend files
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 | with:
18 | ref: ${{ github.event.pull_request.head.sha }}
19 | repository: ${{github.event.pull_request.head.repo.full_name}}
20 | persist-credentials: false
21 |
22 | - name: Setup nodeJS
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: 12
26 |
27 | - name: Install the packages necessary for lint checking
28 | run: npm install
29 |
30 | - name: Check linting
31 | run: npm run lint-check
32 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js Test, Build and Push to DockerHub
5 |
6 | on:
7 | push:
8 | tags:
9 | - '*'
10 | branches:
11 | - master
12 | pull_request:
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | strategy:
20 | matrix:
21 | node-version: [12.x]
22 |
23 | steps:
24 | - uses: actions/checkout@v2
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v1
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 | - name: "Install Liqodash Dependencies"
30 | run: npm install
31 |
32 | - name: "Launch Liqodash Tests"
33 | run: npm test
34 |
35 | - name: Coveralls
36 | if: success()
37 | uses: coverallsapp/github-action@master
38 | with:
39 | github-token: ${{ secrets.GITHUB_TOKEN }}
40 |
41 | - name: Publish Docker (Non-master branch)
42 | uses: docker/build-push-action@v1
43 | if: github.ref != 'refs/heads/master' && !startsWith(github.ref, 'refs/tags/v')
44 | with:
45 | username: ${{ secrets.DOCKER_USERNAME }}
46 | password: ${{ secrets.DOCKER_PASSWORD }}
47 | repository: liqo/dashboard-ci
48 | tags: latest, ${{ github.sha }}
49 |
50 | - name: Publish Docker (Master Branch)
51 | uses: docker/build-push-action@v1
52 | if: github.ref == 'refs/heads/master'
53 | with:
54 | username: ${{ secrets.DOCKER_USERNAME }}
55 | password: ${{ secrets.DOCKER_PASSWORD }}
56 | repository: liqo/dashboard
57 | tags: latest, ${{ github.sha }}
58 |
59 | - name: Get the tag for version (Release)
60 | id: get_version
61 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
62 | if: startsWith(github.ref, 'refs/tags/v')
63 |
64 | - name: Build and Publish Laboratory-Operator image (Repository)
65 | uses: docker/build-push-action@v1
66 | if: startsWith(github.ref, 'refs/tags/v')
67 | with:
68 | username: ${{ secrets.DOCKER_USERNAME }}
69 | password: ${{ secrets.DOCKER_PASSWORD }}
70 | repository: liqo/dashboard
71 | tags: latest, ${{ steps.get_version.outputs.VERSION }}
72 |
73 | release:
74 | runs-on: ubuntu-latest
75 | needs: [build]
76 | steps:
77 | - uses: 8398a7/action-slack@v3.8.3
78 | with:
79 | status: ${{ job.status }}
80 | author_name: Integration Test # default: 8398a7@action-slack
81 | env:
82 | GITHUB_TOKEN: ${{ secrets.CI_TOKEN }} # required
83 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} # required
84 | if: always() && github.ref == 'refs/heads/master' # Pick up events even if the job fails or is canceled.
85 |
86 |
--------------------------------------------------------------------------------
/.github/workflows/rebase.yml:
--------------------------------------------------------------------------------
1 | name: Rebase
2 | on:
3 | issue_comment:
4 | types: [created]
5 | jobs:
6 | rebase:
7 | name: Rebase
8 | if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@master
12 | with:
13 | fetch-depth: 0
14 | token: ${{ secrets.CI_TOKEN }}
15 | - name: Automatic Rebase
16 | uses: cirrus-actions/rebase@1.3
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.CI_TOKEN }}
19 |
20 | - name: Create comment (Success)
21 | uses: peter-evans/create-or-update-comment@v1
22 | with:
23 | token: ${{ secrets.CI_TOKEN }}
24 | issue-number: ${{ github.event.issue.number }}
25 | body: |
26 | Rebase status: ${{ job.status }}!
27 | if: always()
28 |
29 | always_job:
30 | name: Always run job
31 | runs-on: ubuntu-latest
32 | steps:
33 | - name: Always run
34 | run: echo "This job is used to prevent the workflow to fail when all other jobs are skipped."
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | dist
4 | .idea
5 | coverage
6 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # THIS FILE NEEDS TO BE IN VERSION CONTROL BECAUSE IT TELLS WHAT TO DO DURING THE PRE-COMMIT HOOK
3 | . "$(dirname "$0")/_/husky.sh"
4 |
5 | npx --no-install lint-staged
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at crownlabs.polito@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Builder image for the webservice
2 | FROM node:14-alpine as builder
3 |
4 | # Download git to fetch the kubernetes repo
5 | RUN apk add --no-cache --update git openssh
6 |
7 | ## Switch to an unprivileged user (avoids problems with npm)
8 | USER node
9 |
10 | ## Set the working directory and copy the source code
11 | RUN mkdir --parent /tmp/webservice
12 | COPY --chown=node:node . /tmp/webservice
13 | WORKDIR /tmp/webservice
14 |
15 | ## Install the dependencies and build
16 | RUN npm install
17 | RUN npm run build
18 |
19 | # Final image to export the service
20 | FROM nginx:1.19
21 |
22 | ## Copy the different files
23 | COPY --chown=nginx:nginx --from=builder /tmp/webservice/dist /usr/share/nginx/html
24 | COPY --chown=nginx:nginx nginx.conf /etc/nginx/conf.d/default.conf
25 | COPY --chown=nginx:nginx docker-entrypoint.sh /
26 |
27 | ## Add permissions for the nginx user
28 | RUN chown -R nginx:nginx /usr/share/nginx/html && \
29 | chown -R nginx:nginx /var/cache/nginx && \
30 | chown -R nginx:nginx /var/log/nginx && \
31 | chown -R nginx:nginx /etc/nginx/conf.d && \
32 | touch /var/run/nginx.pid && \
33 | chown -R nginx:nginx /var/run/nginx.pid
34 |
35 | RUN chmod +x docker-entrypoint.sh
36 |
37 | ENTRYPOINT ["/docker-entrypoint.sh"]
38 |
--------------------------------------------------------------------------------
/__mocks__/401.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": {
3 | "kind": "Status",
4 | "apiVersion": "v1",
5 | "metadata": {},
6 | "status": "Failure",
7 | "message": "Unauthorized",
8 | "reason": "Unauthorized",
9 | "code": 401,
10 | "response": {
11 | "_fetchResponse": {
12 | "status": 401
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/__mocks__/403.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": {
3 | "kind": "Status",
4 | "apiVersion": "v1",
5 | "metadata": {},
6 | "status": "Failure",
7 | "message": "Forbidden",
8 | "reason": "Forbidden",
9 | "code": 403,
10 | "response": {
11 | "_fetchResponse": {
12 | "status": 403
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/__mocks__/404.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": {
3 | "kind": "Status",
4 | "apiVersion": "v1",
5 | "metadata": {},
6 | "status": "Failure",
7 | "message": "NotFound",
8 | "reason": "NotFound",
9 | "code": 404,
10 | "response": {
11 | "_fetchResponse": {
12 | "status": 404
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/__mocks__/409.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": {
3 | "kind": "Status",
4 | "apiVersion": "v1",
5 | "metadata": {},
6 | "status": "Failure",
7 | "message": "Conflict",
8 | "reason": "Conflict",
9 | "code": 409,
10 | "response": {
11 | "_fetchResponse": {
12 | "status": 409
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/__mocks__/500.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": {
3 | "code": 500
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/__mocks__/advertisement.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "protocol.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "protocol.liqo.io/v1",
6 | "kind": "Advertisement",
7 | "metadata": {
8 | "name": "advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53",
9 | "ownerReferences": [
10 | {
11 | "apiVersion": "discovery.liqo.io/v1",
12 | "controller": true,
13 | "kind": "ForeignCluster",
14 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53"
15 | }
16 | ],
17 | "resourceVersion": "00000",
18 | "selfLink": "/apis/protocol.liqo.io/v1/advertisements/advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53",
19 | "uid": "cb97e3a7-22c3-4bde-bbb5-18a5096ad2c5"
20 | },
21 | "spec": {
22 | "clusterId": "8d73c01a-f23a-45dc-822b-7d3232683f53",
23 | "images": [
24 | {
25 | "names": ["docker.io/kindest/kindnetd:0.5.4"],
26 | "sizeBytes": 113207016
27 | },
28 | {
29 | "names": [
30 | "docker.io/liqo/init-vkubelet@sha256:ae4eab11e1d573a0be540a0047184db8b0af78d492ade10e670e3fc014dccdb2",
31 | "docker.io/liqo/init-vkubelet:latest"
32 | ],
33 | "sizeBytes": 50892533
34 | },
35 | {
36 | "names": [
37 | "docker.io/liqo/init-vkubelet@sha256:ae4eab11e1d573a0be540a0047184db8b0af78d492ade10e670e3fc014dccdb2",
38 | "docker.io/liqo/init-vkubelet:latest"
39 | ],
40 | "sizeBytes": 50892533
41 | }
42 | ],
43 | "kubeConfigRef": {
44 | "name": "vk-kubeconfig-secret-8d73c01a-f23a-45dc-822b-7d3232683f53",
45 | "namespace": "liqo"
46 | },
47 | "limitRange": {
48 | "limits": [
49 | {
50 | "max": {
51 | "cpu": "100m",
52 | "memory": "390Mi"
53 | },
54 | "type": ""
55 | }
56 | ]
57 | },
58 | "network": {
59 | "gatewayIP": "172.18.0.2",
60 | "gatewayPrivateIP": "192.168.1.1",
61 | "podCIDR": "10.244.0.0/16"
62 | },
63 | "prices": {
64 | "cpu": "1",
65 | "docker.io/kindest/kindnetd:0.5.4": "5",
66 | "docker.io/liqo/advertisement-operator:latest": "5",
67 | "docker.io/liqo/advertisement-operator@sha256:630ff31f2a81cb5fcc99e4d112515276b0d8d539b0131a0c374c5ac0d4008671": "5",
68 | "docker.io/liqo/discovery:latest": "5",
69 | "memory": "2m"
70 | },
71 | "resourceQuota": {
72 | "hard": {
73 | "cpu": "2145m",
74 | "memory": "5665988659",
75 | "pods": "33"
76 | }
77 | },
78 | "timeToLive": "2020-09-02T18:53:58Z",
79 | "timestamp": "2020-09-02T18:23:58Z"
80 | },
81 | "status": {
82 | "advertisementStatus": "Accepted",
83 | "localRemappedPodCIDR": "10.22.0.0/16",
84 | "remoteRemappedPodCIDR": "10.26.0.0/16",
85 | "tunnelEndpointKey": {
86 | "name": "",
87 | "namespace": ""
88 | },
89 | "vkCreated": true,
90 | "vkReference": {
91 | "name": "liqo-test"
92 | },
93 | "vnodeReference": {
94 | "name": "liqo-test"
95 | }
96 | }
97 | }
98 | ],
99 | "kind": "AdvertisementList",
100 | "metadata": {
101 | "continue": "",
102 | "resourceVersion": "00000",
103 | "selfLink": "/apis/protocol.liqo.io/v1/advertisements"
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/__mocks__/advertisement_noStatus.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "protocol.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "protocol.liqo.io/v1",
6 | "kind": "Advertisement",
7 | "metadata": {
8 | "name": "advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53",
9 | "resourceVersion": "000000000",
10 | "selfLink": "/apis/protocol.liqo.io/v1/advertisements/advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53"
11 | },
12 | "spec": {
13 | "clusterId": "test",
14 | "network": {
15 | "gatewayIP": "test",
16 | "gatewayPrivateIP": "test",
17 | "podCIDR": "test"
18 | },
19 | "timeToLive": "",
20 | "timestamp": ""
21 | }
22 | }
23 | ],
24 | "kind": "AdvertisementList",
25 | "metadata": {
26 | "continue": "",
27 | "resourceVersion": "000000",
28 | "selfLink": "/apis/protocol.liqo.io/v1/advertisements"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/__mocks__/advertisement_notAccepted.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "protocol.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "protocol.liqo.io/v1",
6 | "kind": "Advertisement",
7 | "metadata": {
8 | "name": "advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53",
9 | "resourceVersion": "000000000",
10 | "selfLink": "/apis/protocol.liqo.io/v1/advertisements/advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53"
11 | },
12 | "spec": {
13 | "clusterId": "test",
14 | "network": {
15 | "gatewayIP": "test",
16 | "gatewayPrivateIP": "test",
17 | "podCIDR": "test"
18 | },
19 | "timeToLive": "",
20 | "timestamp": ""
21 | },
22 | "status": {
23 | "advertisementStatus": "",
24 | "foreignNetwork": {
25 | "gatewayIP": "",
26 | "gatewayPrivateIP": "",
27 | "podCIDR": ""
28 | },
29 | "observedGeneration": 1,
30 | "remoteRemappedPodCIDR": "",
31 | "tunnelEndpointKey": {
32 | "name": "",
33 | "namespace": ""
34 | },
35 | "vkCreated": true
36 | }
37 | }
38 | ],
39 | "kind": "AdvertisementList",
40 | "metadata": {
41 | "continue": "",
42 | "resourceVersion": "000000",
43 | "selfLink": "/apis/protocol.liqo.io/v1/advertisements"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/__mocks__/advertisement_refused.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "protocol.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "protocol.liqo.io/v1",
6 | "kind": "Advertisement",
7 | "metadata": {
8 | "name": "advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53",
9 | "resourceVersion": "000000000",
10 | "selfLink": "/apis/protocol.liqo.io/v1/advertisements/advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53"
11 | },
12 | "spec": {
13 | "clusterId": "test",
14 | "network": {
15 | "gatewayIP": "test",
16 | "gatewayPrivateIP": "test",
17 | "podCIDR": "test"
18 | },
19 | "timeToLive": "",
20 | "timestamp": ""
21 | },
22 | "status": {
23 | "advertisementStatus": "Refused",
24 | "foreignNetwork": {
25 | "gatewayIP": "",
26 | "gatewayPrivateIP": "",
27 | "podCIDR": ""
28 | },
29 | "observedGeneration": 1,
30 | "remoteRemappedPodCIDR": "",
31 | "tunnelEndpointKey": {
32 | "name": "",
33 | "namespace": ""
34 | },
35 | "vkCreated": true
36 | }
37 | }
38 | ],
39 | "kind": "AdvertisementList",
40 | "metadata": {
41 | "continue": "",
42 | "resourceVersion": "000000",
43 | "selfLink": "/apis/protocol.liqo.io/v1/advertisements"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/__mocks__/apiextension.k8s.io.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "apiVersion": "v1",
4 | "groupVersion": "apiextensions.k8s.io/v1",
5 | "resources": [
6 | {
7 | "name": "customresourcedefinitions",
8 | "singularName": "",
9 | "namespaced": false,
10 | "kind": "CustomResourceDefinition",
11 | "verbs": [
12 | "create",
13 | "delete",
14 | "deletecollection",
15 | "get",
16 | "list",
17 | "patch",
18 | "update",
19 | "watch"
20 | ],
21 | "shortNames": ["crd", "crds"],
22 | "storageVersionHash": "jfWCUB31mvA="
23 | },
24 | {
25 | "name": "customresourcedefinitions/status",
26 | "singularName": "",
27 | "namespaced": false,
28 | "kind": "CustomResourceDefinition",
29 | "verbs": ["get", "patch", "update"]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/__mocks__/apis.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIGroupList",
3 | "apiVersion": "v1",
4 | "groups": [
5 | {
6 | "name": "apps",
7 | "versions": [
8 | {
9 | "groupVersion": "apps/v1",
10 | "version": "v1"
11 | }
12 | ],
13 | "preferredVersion": {
14 | "groupVersion": "apps/v1",
15 | "version": "v1"
16 | }
17 | },
18 | {
19 | "name": "apiextensions.k8s.io",
20 | "versions": [
21 | {
22 | "groupVersion": "apiextensions.k8s.io/v1",
23 | "version": "v1"
24 | },
25 | {
26 | "groupVersion": "apiextensions.k8s.io/v1beta1",
27 | "version": "v1beta1"
28 | }
29 | ],
30 | "preferredVersion": {
31 | "groupVersion": "apiextensions.k8s.io/v1",
32 | "version": "v1"
33 | }
34 | },
35 | {
36 | "name": "config.liqo.io",
37 | "versions": [
38 | {
39 | "groupVersion": "config.liqo.io/v1alpha1",
40 | "version": "v1alpha1"
41 | }
42 | ],
43 | "preferredVersion": {
44 | "groupVersion": "config.liqo.io/v1alpha1",
45 | "version": "v1alpha1"
46 | }
47 | },
48 | {
49 | "name": "dashboard.liqo.io",
50 | "versions": [
51 | {
52 | "groupVersion": "dashboard.liqo.io/v1alpha1",
53 | "version": "v1alpha1"
54 | }
55 | ],
56 | "preferredVersion": {
57 | "groupVersion": "dashboard.liqo.io/v1alpha1",
58 | "version": "v1alpha1"
59 | }
60 | },
61 | {
62 | "name": "discovery.liqo.io",
63 | "versions": [
64 | {
65 | "groupVersion": "discovery.liqo.io/v1alpha1",
66 | "version": "v1alpha1"
67 | }
68 | ],
69 | "preferredVersion": {
70 | "groupVersion": "discovery.liqo.io/v1alpha1",
71 | "version": "v1alpha1"
72 | }
73 | },
74 | {
75 | "name": "net.liqo.io",
76 | "versions": [
77 | {
78 | "groupVersion": "net.liqo.io/v1alpha1",
79 | "version": "v1alpha1"
80 | }
81 | ],
82 | "preferredVersion": {
83 | "groupVersion": "net.liqo.io/v1alpha1",
84 | "version": "v1alpha1"
85 | }
86 | },
87 | {
88 | "name": "scheduling.liqo.io",
89 | "versions": [
90 | {
91 | "groupVersion": "scheduling.liqo.io/v1alpha1",
92 | "version": "v1alpha1"
93 | }
94 | ],
95 | "preferredVersion": {
96 | "groupVersion": "scheduling.liqo.io/v1alpha1",
97 | "version": "v1alpha1"
98 | }
99 | },
100 | {
101 | "name": "sharing.liqo.io",
102 | "versions": [
103 | {
104 | "groupVersion": "sharing.liqo.io/v1alpha1",
105 | "version": "v1alpha1"
106 | }
107 | ],
108 | "preferredVersion": {
109 | "groupVersion": "sharing.liqo.io/v1alpha1",
110 | "version": "v1alpha1"
111 | }
112 | }
113 | ]
114 | }
115 |
--------------------------------------------------------------------------------
/__mocks__/apiv1.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "groupVersion": "v1",
4 | "resources": [
5 | {
6 | "name": "pods",
7 | "singularName": "",
8 | "namespaced": true,
9 | "kind": "Pod",
10 | "verbs": [
11 | "create",
12 | "delete",
13 | "deletecollection",
14 | "get",
15 | "list",
16 | "patch",
17 | "update",
18 | "watch"
19 | ],
20 | "shortNames": ["po"],
21 | "categories": ["all"]
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/__mocks__/config.liqo.io.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "apiVersion": "v1",
4 | "groupVersion": "config.liqo.io/v1alpha1",
5 | "resources": [
6 | {
7 | "name": "clusterconfigs",
8 | "singularName": "clusterconfig",
9 | "namespaced": false,
10 | "kind": "ClusterConfig",
11 | "verbs": [
12 | "delete",
13 | "deletecollection",
14 | "get",
15 | "list",
16 | "patch",
17 | "create",
18 | "update",
19 | "watch"
20 | ],
21 | "storageVersionHash": "Zvt6E4r+7OI="
22 | },
23 | {
24 | "name": "clusterconfigs/status",
25 | "singularName": "",
26 | "namespaced": false,
27 | "kind": "ClusterConfig",
28 | "verbs": ["get", "patch", "update"]
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/__mocks__/configmap_clusterID.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "ConfigMapList",
3 | "apiVersion": "v1",
4 | "metadata": {
5 | "selfLink": "/api/v1/namespaces/liqo/configmaps",
6 | "resourceVersion": "00000"
7 | },
8 | "items": [
9 | {
10 | "metadata": {
11 | "name": "cluster-id",
12 | "namespace": "liqo",
13 | "selfLink": "/api/v1/namespaces/liqo/configmaps/cluster-id",
14 | "resourceVersion": "00000"
15 | },
16 | "data": {
17 | "cluster-id": "10e3e821-8194-4fb1-856f-d917d2fc54c0"
18 | }
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/__mocks__/configs.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "policy.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "policy.liqo.io/v1",
6 | "kind": "ClusterConfig",
7 | "metadata": {
8 | "name": "test",
9 | "resourceVersion": "000000",
10 | "selfLink": "/apis/policy.liqo.io/v1/clusterconfigs/testcc"
11 | },
12 | "spec": {
13 | "advertisementConfig": {
14 | "autoAccept": true,
15 | "maxAcceptableAdvertisement": 10,
16 | "outgoingConfig": {
17 | "resourceSharingPercentage": 63
18 | }
19 | },
20 | "discoveryConfig": {
21 | "autojoin": true,
22 | "domain": "domain-test",
23 | "enableAdvertisement": true,
24 | "enableDiscovery": true,
25 | "clusterName": "LIQO",
26 | "name": "name-test",
27 | "port": 12345,
28 | "service": "service-test",
29 | "updateTime": 3,
30 | "waitTime": 4
31 | },
32 | "liqonetConfig": {
33 | "gatewayPrivateIP": "1.18.1.1",
34 | "reservedSubnets": ["10.244.0.0/16", "10.96.0.0/12"]
35 | }
36 | }
37 | }
38 | ],
39 | "kind": "ClusterConfigList",
40 | "metadata": {
41 | "resourceVersion": "0000000"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/__mocks__/configs_updated.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "policy.liqo.io/v1",
3 | "kind": "ClusterConfig",
4 | "metadata": {
5 | "name": "test",
6 | "resourceVersion": "000000",
7 | "selfLink": "/apis/policy.liqo.io/v1/clusterconfigs/testcc"
8 | },
9 | "spec": {
10 | "advertisementConfig": {
11 | "autoAccept": true,
12 | "maxAcceptableAdvertisement": 10,
13 | "outgoingConfig": {
14 | "resourceSharingPercentage": 63
15 | }
16 | },
17 | "discoveryConfig": {
18 | "autojoin": false,
19 | "domain": "domain-test",
20 | "enableAdvertisement": true,
21 | "enableDiscovery": true,
22 | "clusterName": "LIQO",
23 | "name": "name-test",
24 | "port": 12345,
25 | "service": "service-test",
26 | "updateTime": 3,
27 | "waitTime": 4
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/__mocks__/crd_empty.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "CustomResourceDefinitionList",
3 | "apiVersion": "apiextensions.k8s.io/v1beta1",
4 | "metadata": {
5 | "selfLink": "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions",
6 | "resourceVersion": "896737"
7 | },
8 | "items": []
9 | }
10 |
--------------------------------------------------------------------------------
/__mocks__/dashboard.liqo.io.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "apiVersion": "v1",
4 | "groupVersion": "dashboard.liqo.io/v1alpha1",
5 | "resources": [
6 | {
7 | "name": "dashboardconfigs",
8 | "singularName": "dashboardconfig",
9 | "namespaced": false,
10 | "kind": "DashboardConfig",
11 | "verbs": [
12 | "delete",
13 | "deletecollection",
14 | "get",
15 | "list",
16 | "patch",
17 | "create",
18 | "update",
19 | "watch"
20 | ]
21 | },
22 | {
23 | "name": "dashboardconfigs/status",
24 | "singularName": "",
25 | "namespaced": false,
26 | "kind": "DashboardConfig",
27 | "verbs": ["get", "patch", "update"]
28 | },
29 | {
30 | "name": "views",
31 | "singularName": "view",
32 | "namespaced": true,
33 | "kind": "View",
34 | "verbs": [
35 | "delete",
36 | "deletecollection",
37 | "get",
38 | "list",
39 | "patch",
40 | "create",
41 | "update",
42 | "watch"
43 | ]
44 | },
45 | {
46 | "name": "views/status",
47 | "singularName": "",
48 | "namespaced": true,
49 | "kind": "View",
50 | "verbs": ["get", "patch", "update"]
51 | },
52 | {
53 | "name": "liqodashtests",
54 | "singularName": "liqodashtest",
55 | "namespaced": true,
56 | "kind": "LiqoDashTest",
57 | "verbs": [
58 | "delete",
59 | "deletecollection",
60 | "get",
61 | "list",
62 | "patch",
63 | "create",
64 | "update",
65 | "watch"
66 | ]
67 | }
68 | ]
69 | }
70 |
--------------------------------------------------------------------------------
/__mocks__/discovery.liqo.io.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "apiVersion": "v1",
4 | "groupVersion": "discovery.liqo.io/v1alpha1",
5 | "resources": [
6 | {
7 | "name": "foreignclusters",
8 | "singularName": "foreigncluster",
9 | "namespaced": false,
10 | "kind": "ForeignCluster",
11 | "verbs": [
12 | "delete",
13 | "deletecollection",
14 | "get",
15 | "list",
16 | "patch",
17 | "create",
18 | "update",
19 | "watch"
20 | ],
21 | "storageVersionHash": "wxDkOgq9qmQ="
22 | },
23 | {
24 | "name": "searchdomains",
25 | "singularName": "searchdomain",
26 | "namespaced": false,
27 | "kind": "SearchDomain",
28 | "verbs": [
29 | "delete",
30 | "deletecollection",
31 | "get",
32 | "list",
33 | "patch",
34 | "create",
35 | "update",
36 | "watch"
37 | ],
38 | "storageVersionHash": "TiyPLjS5IPU="
39 | },
40 | {
41 | "name": "peeringrequests",
42 | "singularName": "peeringrequest",
43 | "namespaced": false,
44 | "kind": "PeeringRequest",
45 | "verbs": [
46 | "delete",
47 | "deletecollection",
48 | "get",
49 | "list",
50 | "patch",
51 | "create",
52 | "update",
53 | "watch"
54 | ],
55 | "storageVersionHash": "sA2PCJnQjpk="
56 | }
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/__mocks__/foreigncluster.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "discovery.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "discovery.liqo.io/v1",
6 | "kind": "ForeignCluster",
7 | "metadata": {
8 | "labels": {
9 | "cluster-id": "8d73c01a-f23a-45dc-822b-7d3232683f53",
10 | "discovery-type": "LAN"
11 | },
12 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53",
13 | "resourceVersion": "000000",
14 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters/8d73c01a-f23a-45dc-822b-7d3232683f53"
15 | },
16 | "spec": {
17 | "allowUntrustedCA": true,
18 | "apiUrl": "https://1.1.1.1:1234",
19 | "clusterIdentity": {
20 | "clusterID": "8d73c01a-f23a-45dc-822b-7d3232683f53",
21 | "clusterName": "Cluster-Test"
22 | },
23 | "discoveryType": "LAN",
24 | "join": true,
25 | "namespace": "liqo"
26 | },
27 | "status": {
28 | "incoming": {
29 | "joined": true,
30 | "peeringRequest": {
31 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53"
32 | }
33 | },
34 | "outgoing": {
35 | "advertisement": {
36 | "apiVersion": "protocol.liqo.io/v1",
37 | "kind": "Advertisement",
38 | "name": "advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53"
39 | },
40 | "joined": true,
41 | "remote-peering-request-name": "4317f039-e7b8-40d2-ae20-18d1c47e69f0"
42 | },
43 | "ttl": 3
44 | }
45 | }
46 | ],
47 | "kind": "ForeignClusterList",
48 | "metadata": {
49 | "resourceVersion": "000000",
50 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/__mocks__/foreigncluster_new.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "discovery.liqo.io/v1",
3 | "kind": "ForeignCluster",
4 | "metadata": {
5 | "labels": {
6 | "cluster-id": "9d73c01a-f23a-45dc-822b-7d3232683f53",
7 | "discovery-type": "LAN"
8 | },
9 | "name": "89d73c01a-f23a-45dc-822b-7d3232683f53",
10 | "resourceVersion": "000010",
11 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters/9d73c01a-f23a-45dc-822b-7d3232683f53"
12 | },
13 | "spec": {
14 | "allowUntrustedCA": true,
15 | "apiUrl": "https://1.1.1.2:1234",
16 | "clusterIdentity": {
17 | "clusterID": "9d73c01a-f23a-45dc-822b-7d3232683f53"
18 | },
19 | "discoveryType": "LAN",
20 | "join": true,
21 | "namespace": "liqo"
22 | },
23 | "status": {
24 | "incoming": {
25 | "joined": true,
26 | "peeringRequest": {
27 | "name": "9d73c01a-f23a-45dc-822b-7d3232683f53"
28 | }
29 | },
30 | "outgoing": {
31 | "advertisement": {
32 | "apiVersion": "protocol.liqo.io/v1",
33 | "kind": "Advertisement",
34 | "name": "advertisement-9d73c01a-f23a-45dc-822b-7d3232683f53"
35 | },
36 | "joined": true,
37 | "remote-peering-request-name": "4317f039-e7b8-40d2-ae20-19d1c47e69f0"
38 | },
39 | "ttl": 3
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/__mocks__/foreigncluster_noIncoming.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "discovery.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "discovery.liqo.io/v1",
6 | "kind": "ForeignCluster",
7 | "metadata": {
8 | "labels": {
9 | "cluster-id": "8d73c01a-f23a-45dc-822b-7d3232683f53",
10 | "discovery-type": "LAN"
11 | },
12 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53",
13 | "resourceVersion": "000000",
14 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters/8d73c01a-f23a-45dc-822b-7d3232683f53"
15 | },
16 | "spec": {
17 | "allowUntrustedCA": true,
18 | "apiUrl": "https://1.1.1.1:1234",
19 | "clusterIdentity": {
20 | "clusterID": "8d73c01a-f23a-45dc-822b-7d3232683f53",
21 | "clusterName": "Cluster-Test"
22 | },
23 | "discoveryType": "LAN",
24 | "join": true,
25 | "namespace": "liqo"
26 | },
27 | "status": {
28 | "incoming": {
29 | "joined": false
30 | },
31 | "outgoing": {
32 | "advertisement": {
33 | "apiVersion": "protocol.liqo.io/v1",
34 | "kind": "Advertisement",
35 | "name": "advertisement-8d73c01a-f23a-45dc-822b-7d3232683f53"
36 | },
37 | "joined": true,
38 | "remote-peering-request-name": "4317f039-e7b8-40d2-ae20-18d1c47e69f0"
39 | },
40 | "ttl": 3
41 | }
42 | }
43 | ],
44 | "kind": "ForeignClusterList",
45 | "metadata": {
46 | "resourceVersion": "000000",
47 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/__mocks__/foreigncluster_noJoin.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "discovery.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "discovery.liqo.io/v1",
6 | "kind": "ForeignCluster",
7 | "metadata": {
8 | "labels": {
9 | "cluster-id": "8d73c01a-f23a-45dc-822b-7d3232683f53",
10 | "discovery-type": "LAN"
11 | },
12 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53",
13 | "resourceVersion": "000000",
14 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters/8d73c01a-f23a-45dc-822b-7d3232683f53"
15 | },
16 | "spec": {
17 | "allowUntrustedCA": true,
18 | "apiUrl": "https://1.1.1.1:1234",
19 | "clusterIdentity": {
20 | "clusterID": "8d73c01a-f23a-45dc-822b-7d3232683f53",
21 | "clusterName": "Cluster-Test"
22 | },
23 | "discoveryType": "LAN",
24 | "join": false,
25 | "namespace": "liqo"
26 | },
27 | "status": {
28 | "incoming": {
29 | "joined": false
30 | },
31 | "outgoing": {
32 | "joined": false,
33 | "remote-peering-request-name": "4317f039-e7b8-40d2-ae20-18d1c47e69f0"
34 | },
35 | "ttl": 3
36 | }
37 | }
38 | ],
39 | "kind": "ForeignClusterList",
40 | "metadata": {
41 | "resourceVersion": "000000",
42 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/__mocks__/foreigncluster_noOutgoing.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "discovery.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "discovery.liqo.io/v1",
6 | "kind": "ForeignCluster",
7 | "metadata": {
8 | "labels": {
9 | "cluster-id": "8d73c01a-f23a-45dc-822b-7d3232683f53",
10 | "discovery-type": "LAN"
11 | },
12 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53",
13 | "resourceVersion": "000000",
14 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters/8d73c01a-f23a-45dc-822b-7d3232683f53"
15 | },
16 | "spec": {
17 | "allowUntrustedCA": true,
18 | "apiUrl": "https://1.1.1.1:1234",
19 | "clusterIdentity": {
20 | "clusterID": "8d73c01a-f23a-45dc-822b-7d3232683f53",
21 | "clusterName": "Cluster-Test"
22 | },
23 | "discoveryType": "LAN",
24 | "join": true,
25 | "namespace": "liqo"
26 | },
27 | "status": {
28 | "incoming": {
29 | "joined": true,
30 | "peeringRequest": {
31 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53"
32 | }
33 | },
34 | "outgoing": {
35 | "joined": false,
36 | "remote-peering-request-name": "4317f039-e7b8-40d2-ae20-18d1c47e69f0"
37 | },
38 | "ttl": 3
39 | }
40 | }
41 | ],
42 | "kind": "ForeignClusterList",
43 | "metadata": {
44 | "resourceVersion": "000000",
45 | "selfLink": "/apis/discovery.liqo.io/v1/foreignclusters"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/__mocks__/graph.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "dashboard.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "dashboard.liqo.io/v1",
6 | "kind": "Graph",
7 | "metadata": {
8 | "name": "graph-test",
9 | "namespace": "default",
10 | "resourceVersion": "000000",
11 | "selfLink": "/apis/dashboard.liqo.io/v1/namespaces/nap-test/graphs/graph-test"
12 | },
13 | "spec": {
14 | "group": {
15 | "cluster": "liqo.io/physical",
16 | "type": "nodeType"
17 | },
18 | "neighbors": "neighbors",
19 | "node": "nodeName"
20 | }
21 | }
22 | ],
23 | "kind": "GraphList",
24 | "metadata": {
25 | "continue": "",
26 | "resourceVersion": "0000000"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/__mocks__/histocharts.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "HistoChart",
7 | "metadata": {
8 | "name": "histo-test",
9 | "namespace": "default",
10 | "resourceVersion": "000000",
11 | "selfLink": "/apis/test/v1/namespaces/default/histocharts/histo-test",
12 | "uid": "82ac3885-ffc4-4a0f-a7fd-6f8e72703a11"
13 | },
14 | "spec": {
15 | "labels": "item.name",
16 | "values": "item.cost"
17 | }
18 | }
19 | ],
20 | "kind": "HistoChartList",
21 | "metadata": {
22 | "continue": "",
23 | "resourceVersion": "000000"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/__mocks__/histocharts_wrong.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "HistoChart",
7 | "metadata": {
8 | "name": "histo-test",
9 | "namespace": "default",
10 | "resourceVersion": "000000",
11 | "selfLink": "/apis/test/v1/namespaces/default/histocharts/histo-test",
12 | "uid": "82ac3885-ffc4-4a0f-a7fd-6f8e72703a11"
13 | },
14 | "spec": {
15 | "labels": "item.name_2",
16 | "values": "item.cost_2"
17 | }
18 | }
19 | ],
20 | "kind": "HistoChartList",
21 | "metadata": {
22 | "continue": "",
23 | "resourceVersion": "000000"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/__mocks__/liqodashtest.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "LiqoDashTest",
7 | "metadata": {
8 | "name": "test-1",
9 | "namespace": "default",
10 | "resourceVersion": "0000000",
11 | "selfLink": "/apis/test/v1/namespaces/default/liqodashtests/test-1"
12 | },
13 | "spec": {
14 | "item": [
15 | {
16 | "cost": 11,
17 | "name": "green"
18 | },
19 | {
20 | "cost": 15,
21 | "name": "blue"
22 | }
23 | ]
24 | },
25 | "status": {
26 | "mockStatus": true
27 | }
28 | },
29 | {
30 | "apiVersion": "test/v1",
31 | "kind": "LiqoDashTest",
32 | "metadata": {
33 | "name": "test-2",
34 | "namespace": "default",
35 | "resourceVersion": "0000000",
36 | "selfLink": "/apis/test/v1/namespaces/default/liqodashtests/test-2"
37 | },
38 | "spec": {
39 | "item": [
40 | {
41 | "cost": 3,
42 | "name": "yellow"
43 | },
44 | {
45 | "cost": 11,
46 | "name": "purple"
47 | }
48 | ]
49 | }
50 | }
51 | ],
52 | "kind": "LiqoDashTestList",
53 | "metadata": {
54 | "continue": "",
55 | "resourceVersion": "000000"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/__mocks__/liqodashtest_modifiedCRD.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "name": "liqodashtests.dashboard.liqo.io",
4 | "selfLink": "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/liqodashtests.dashboard.liqo.io",
5 | "resourceVersion": "000001",
6 | "annotations": {
7 | "description": "A test CRD for some implemetation on the liqo-dashboard",
8 | "template": "dashboard.liqo.io/v1/piecharts/pie-test-123455"
9 | }
10 | },
11 | "spec": {
12 | "group": "dashboard.liqo.io",
13 | "version": "v1",
14 | "names": {
15 | "plural": "liqodashtests",
16 | "singular": "liqodashtest",
17 | "kind": "LiqoDashTestMod",
18 | "listKind": "LiqoDashTestList"
19 | },
20 | "scope": "Namespaced",
21 | "validation": {
22 | "openAPIV3Schema": {
23 | "type": "object",
24 | "properties": {
25 | "apiVersion": {
26 | "type": "string"
27 | },
28 | "kind": {
29 | "type": "string"
30 | },
31 | "metadata": {
32 | "type": "object"
33 | },
34 | "spec": {
35 | "properties": {
36 | "item": {
37 | "description": "Collection of items",
38 | "type": "array",
39 | "items": {
40 | "type": "object",
41 | "properties": {
42 | "cost": {
43 | "description": "Cost of the item",
44 | "type": "integer"
45 | },
46 | "name": {
47 | "description": "Name of the item",
48 | "type": "string"
49 | }
50 | }
51 | }
52 | }
53 | }
54 | }
55 | }
56 | }
57 | }
58 | },
59 | "status": {}
60 | }
61 |
--------------------------------------------------------------------------------
/__mocks__/liqodashtest_new.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "kind": "LiqoDashTest",
4 | "metadata": {
5 | "name": "test-3",
6 | "namespace": "default",
7 | "resourceVersion": "0000000",
8 | "selfLink": "/apis/test/v1/namespaces/default/liqodashtests/test-3"
9 | },
10 | "spec": {
11 | "item": [
12 | {
13 | "cost": 1,
14 | "name": "cyan"
15 | },
16 | {
17 | "cost": 2,
18 | "name": "orange"
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/__mocks__/liqodashtest_noSpec_noStatus.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "LiqoDashTest",
7 | "metadata": {
8 | "name": "test-1",
9 | "namespace": "default",
10 | "resourceVersion": "0000000",
11 | "selfLink": "/apis/test/v1/namespaces/default/liqodashtests/test-1"
12 | }
13 | },
14 | {
15 | "apiVersion": "test/v1",
16 | "kind": "LiqoDashTest",
17 | "metadata": {
18 | "name": "test-2",
19 | "namespace": "default",
20 | "resourceVersion": "0000000",
21 | "selfLink": "/apis/test/v1/namespaces/default/liqodashtests/test-2"
22 | },
23 | "spec": {
24 | "item": [
25 | {
26 | "cost": 3,
27 | "name": "yellow"
28 | },
29 | {
30 | "cost": 11,
31 | "name": "purple"
32 | }
33 | ]
34 | }
35 | }
36 | ],
37 | "kind": "LiqoDashTestList",
38 | "metadata": {
39 | "continue": "",
40 | "resourceVersion": "000000"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/__mocks__/liqodashtest_update.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "kind": "LiqoDashTest",
4 | "metadata": {
5 | "name": "test-1",
6 | "namespace": "default",
7 | "resourceVersion": "0000001",
8 | "selfLink": "/apis/test/v1/namespaces/default/liqodashtests/test-1"
9 | },
10 | "spec": {
11 | "item": [
12 | {
13 | "cost": 13,
14 | "name": "green"
15 | },
16 | {
17 | "cost": 15,
18 | "name": "purple"
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/__mocks__/manyResources.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "ManyResource",
7 | "metadata": {
8 | "name": "test-1",
9 | "resourceVersion": "000000",
10 | "selfLink": "/apis/test/v1/manyresources/test-1"
11 | },
12 | "spec": {
13 | "cidr": "",
14 | "deleted": "",
15 | "node": "",
16 | "state": ""
17 | }
18 | },
19 | {
20 | "apiVersion": "test/v1",
21 | "kind": "ManyResource",
22 | "metadata": {
23 | "name": "test-2",
24 | "resourceVersion": "000000",
25 | "selfLink": "/apis/test/v1/manyresources/test-2"
26 | },
27 | "spec": {
28 | "cidr": "",
29 | "deleted": "",
30 | "node": "",
31 | "state": ""
32 | }
33 | },
34 | {
35 | "apiVersion": "test/v1",
36 | "kind": "ManyResource",
37 | "metadata": {
38 | "name": "test-3",
39 | "resourceVersion": "000000",
40 | "selfLink": "/apis/test/v1/manyresources/test-3"
41 | },
42 | "spec": {
43 | "cidr": "",
44 | "deleted": "",
45 | "node": "",
46 | "state": ""
47 | }
48 | },
49 | {
50 | "apiVersion": "test/v1",
51 | "kind": "ManyResource",
52 | "metadata": {
53 | "name": "test-4",
54 | "resourceVersion": "000000",
55 | "selfLink": "/apis/test/v1/manyresources/test-4"
56 | },
57 | "spec": {
58 | "cidr": "",
59 | "deleted": "",
60 | "node": "",
61 | "state": ""
62 | }
63 | },
64 | {
65 | "apiVersion": "test/v1",
66 | "kind": "ManyResource",
67 | "metadata": {
68 | "name": "test-5",
69 | "resourceVersion": "000000",
70 | "selfLink": "/apis/test/v1/manyresources/test-5"
71 | },
72 | "spec": {
73 | "cidr": "",
74 | "deleted": "",
75 | "node": "",
76 | "state": ""
77 | }
78 | },
79 | {
80 | "apiVersion": "test/v1",
81 | "kind": "ManyResource",
82 | "metadata": {
83 | "name": "test-6",
84 | "resourceVersion": "000000",
85 | "selfLink": "/apis/test/v1/manyresources/test-6"
86 | },
87 | "spec": {
88 | "cidr": "",
89 | "deleted": "",
90 | "node": "",
91 | "state": ""
92 | }
93 | },
94 | {
95 | "apiVersion": "test/v1",
96 | "kind": "ManyResource",
97 | "metadata": {
98 | "name": "test-7",
99 | "resourceVersion": "000000",
100 | "selfLink": "/apis/test/v1/manyresources/test-7"
101 | },
102 | "spec": {
103 | "cidr": "",
104 | "deleted": "",
105 | "node": "",
106 | "state": ""
107 | }
108 | },
109 | {
110 | "apiVersion": "test/v1",
111 | "kind": "ManyResource",
112 | "metadata": {
113 | "name": "test-8",
114 | "resourceVersion": "000000",
115 | "selfLink": "/apis/test/v1/manyresources/test-8"
116 | },
117 | "spec": {
118 | "cidr": "",
119 | "deleted": "",
120 | "node": "",
121 | "state": ""
122 | }
123 | }
124 | ],
125 | "kind": "ManyResourceList",
126 | "metadata": {
127 | "continue": "",
128 | "resourceVersion": "80733027",
129 | "selfLink": "/apis/test/v1/manyresources"
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/__mocks__/namespaces.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "NamespaceList",
3 | "apiVersion": "v1",
4 | "metadata": {
5 | "selfLink": "/api/v1/namespaces",
6 | "resourceVersion": "00000"
7 | },
8 | "items": [
9 | {
10 | "metadata": {
11 | "name": "default",
12 | "selfLink": "/api/v1/namespaces/default",
13 | "resourceVersion": "001"
14 | }
15 | },
16 | {
17 | "metadata": {
18 | "name": "liqo",
19 | "selfLink": "/api/v1/namespaces/liqo",
20 | "resourceVersion": "002"
21 | }
22 | },
23 | {
24 | "metadata": {
25 | "name": "test",
26 | "selfLink": "/api/v1/namespaces/test",
27 | "resourceVersion": "003",
28 | "labels": {
29 | "liqo.io/enabled": "true"
30 | }
31 | }
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/__mocks__/net.liqo.io.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "apiVersion": "v1",
4 | "groupVersion": "net.liqo.io/v1alpha1",
5 | "resources": [
6 | {
7 | "name": "networkconfigs",
8 | "singularName": "networkconfig",
9 | "namespaced": false,
10 | "kind": "NetworkConfig",
11 | "verbs": [
12 | "delete",
13 | "deletecollection",
14 | "get",
15 | "list",
16 | "patch",
17 | "create",
18 | "update",
19 | "watch"
20 | ],
21 | "storageVersionHash": "qsc7utRJ/kY="
22 | },
23 | {
24 | "name": "networkconfigs/status",
25 | "singularName": "",
26 | "namespaced": false,
27 | "kind": "NetworkConfig",
28 | "verbs": ["get", "patch", "update"]
29 | },
30 | {
31 | "name": "tunnelendpoints",
32 | "singularName": "tunnelendpoint",
33 | "namespaced": false,
34 | "kind": "TunnelEndpoint",
35 | "verbs": [
36 | "delete",
37 | "deletecollection",
38 | "get",
39 | "list",
40 | "patch",
41 | "create",
42 | "update",
43 | "watch"
44 | ],
45 | "storageVersionHash": "R4sUOfG4c7g="
46 | },
47 | {
48 | "name": "tunnelendpoints/status",
49 | "singularName": "",
50 | "namespaced": false,
51 | "kind": "TunnelEndpoint",
52 | "verbs": ["get", "patch", "update"]
53 | }
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/__mocks__/no_Ann_noRes_noSch.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "crd.projectcalico.org/v1",
3 | "items": [],
4 | "kind": "NoAnnNoResNoSchemaList",
5 | "metadata": {
6 | "resourceVersion": "0000000"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/__mocks__/nodes.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "NodeList",
3 | "apiVersion": "v1",
4 | "metadata": {
5 | "selfLink": "/api/v1/nodes",
6 | "resourceVersion": "000000"
7 | },
8 | "items": [
9 | {
10 | "metadata": {
11 | "name": "liqo1-control-plane",
12 | "selfLink": "/api/v1/nodes/liqo1-control-plane",
13 | "resourceVersion": "000000",
14 | "labels": {
15 | "liqonet.liqo.io/gateway": "true"
16 | },
17 | "annotations": {
18 | "kubeadm.alpha.kubernetes.io/cri-socket": "/run/containerd/containerd.sock",
19 | "node.alpha.kubernetes.io/ttl": "0",
20 | "volumes.kubernetes.io/controller-managed-attach-detach": "true"
21 | }
22 | },
23 | "spec": {
24 | "podCIDR": "10.244.0.0/24",
25 | "podCIDRs": ["10.244.0.0/24"]
26 | },
27 | "status": {
28 | "capacity": {
29 | "cpu": "8",
30 | "ephemeral-storage": "263174212Ki",
31 | "hugepages-2Mi": "0",
32 | "memory": "19615096Ki",
33 | "pods": "110"
34 | },
35 | "allocatable": {
36 | "cpu": "8",
37 | "ephemeral-storage": "263174212Ki",
38 | "hugepages-2Mi": "0",
39 | "memory": "19615096Ki",
40 | "pods": "110"
41 | }
42 | }
43 | },
44 | {
45 | "metadata": {
46 | "name": "liqo-test",
47 | "selfLink": "/api/v1/nodes/liqo-test",
48 | "resourceVersion": "000000",
49 | "labels": {
50 | "type": "virtual-node"
51 | },
52 | "annotations": {
53 | "kubeadm.alpha.kubernetes.io/cri-socket": "/run/containerd/containerd.sock",
54 | "node.alpha.kubernetes.io/ttl": "0",
55 | "volumes.kubernetes.io/controller-managed-attach-detach": "true"
56 | }
57 | },
58 | "spec": {
59 | "podCIDR": "10.244.0.0/24",
60 | "podCIDRs": ["10.244.0.0/24"]
61 | },
62 | "status": {
63 | "capacity": {
64 | "cpu": "1689m",
65 | "ephemeral-storage": "263174212Ki",
66 | "hugepages-2Mi": "0",
67 | "memory": "5200947609",
68 | "pods": "33"
69 | },
70 | "allocatable": {
71 | "cpu": "1689m",
72 | "ephemeral-storage": "263174212Ki",
73 | "hugepages-2Mi": "0",
74 | "memory": "5200947609",
75 | "pods": "33"
76 | }
77 | }
78 | }
79 | ]
80 | }
81 |
--------------------------------------------------------------------------------
/__mocks__/nodes_metrics.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "NodeMetricsList",
3 | "apiVersion": "metrics.k8s.io/v1beta1",
4 | "metadata": {
5 | "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes"
6 | },
7 | "items": [
8 | {
9 | "metadata": {
10 | "name": "liqo1-control-plane",
11 | "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/liqo1-control-plane"
12 | },
13 | "window": "30s",
14 | "usage": {
15 | "cpu": "385786574n",
16 | "memory": "1173984Ki"
17 | }
18 | },
19 | {
20 | "metadata": {
21 | "name": "liqo-test",
22 | "selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/liqo-test"
23 | },
24 | "window": "30s",
25 | "usage": {
26 | "cpu": "385786574n",
27 | "memory": "1173984Ki"
28 | }
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/__mocks__/peeringrequest.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "discovery.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "discovery.liqo.io/v1",
6 | "kind": "PeeringRequest",
7 | "metadata": {
8 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53",
9 | "ownerReferences": [
10 | {
11 | "apiVersion": "discovery.liqo.io/v1",
12 | "controller": true,
13 | "kind": "ForeignCluster",
14 | "name": "8d73c01a-f23a-45dc-822b-7d3232683f53"
15 | }
16 | ],
17 | "resourceVersion": "00000",
18 | "selfLink": "/apis/discovery.liqo.io/v1/peeringrequests/8d73c01a-f23a-45dc-822b-7d3232683f53"
19 | },
20 | "spec": {
21 | "clusterID": "8d73c01a-f23a-45dc-822b-7d3232683f53",
22 | "namespace": "liqo",
23 | "originClusterSets": {
24 | "allowUntrustedCA": true
25 | }
26 | },
27 | "status": {}
28 | }
29 | ],
30 | "kind": "PeeringRequestList",
31 | "metadata": {
32 | "resourceVersion": "000000",
33 | "selfLink": "/apis/discovery.liqo.io/v1/peeringrequests"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/__mocks__/piecharts.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "PieChart",
7 | "metadata": {
8 | "name": "pie-test",
9 | "namespace": "nap-test",
10 | "resourceVersion": "000000",
11 | "selfLink": "/apis/test/v1/namespaces/default/piecharts/pie-test"
12 | },
13 | "spec": {
14 | "labels": "item.name",
15 | "values": "item.cost"
16 | }
17 | },
18 | {
19 | "apiVersion": "test/v1",
20 | "kind": "PieChart",
21 | "metadata": {
22 | "name": "pie-test-123455",
23 | "namespace": "nap-test",
24 | "resourceVersion": "00000000",
25 | "selfLink": "/apis/test/v1/namespaces/default/piecharts/pie-test-123455"
26 | },
27 | "spec": {
28 | "labels": "item.name",
29 | "values": "item.cost"
30 | }
31 | }
32 | ],
33 | "kind": "PieChartList",
34 | "metadata": {
35 | "resourceVersion": "000000"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/__mocks__/piecharts_wrong.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "PieChart",
7 | "metadata": {
8 | "name": "pie-test",
9 | "namespace": "nap-test",
10 | "resourceVersion": "000000",
11 | "selfLink": "/apis/test/v1/namespaces/default/piecharts/pie-test"
12 | },
13 | "spec": {
14 | "labels": "item.name_2",
15 | "values": "item.cost_2"
16 | }
17 | }
18 | ],
19 | "kind": "PieChartList",
20 | "metadata": {
21 | "resourceVersion": "000000"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/__mocks__/pod.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "v1",
3 | "kind": "Pod",
4 | "metadata": {
5 | "name": "hello-world-deployment-6756549f5-x66v9",
6 | "generateName": "hello-world-deployment-6756549f5-",
7 | "namespace": "test",
8 | "selfLink": "/api/v1/namespaces/test/pods/hello-world-deployment-6756549f5-x66v9",
9 | "labels": {
10 | "app": "hello-world",
11 | "pod-template-hash": "6756549f5"
12 | },
13 | "ownerReferences": [
14 | {
15 | "apiVersion": "apps/v1",
16 | "kind": "ReplicaSet",
17 | "name": "hello-world-deployment-6756549f5",
18 | "uid": "419b8224-d91f-4f83-b972-e0fe158d7500",
19 | "controller": true,
20 | "blockOwnerDeletion": true
21 | }
22 | ]
23 | },
24 | "spec": {
25 | "volumes": [
26 | {
27 | "name": "default-token-mbjjj",
28 | "secret": {
29 | "secretName": "default-token-mbjjj",
30 | "defaultMode": 420
31 | }
32 | }
33 | ],
34 | "containers": [
35 | {
36 | "name": "hello-world",
37 | "image": "bhargavshah86/kube-test:v0.1",
38 | "ports": [
39 | {
40 | "containerPort": 80,
41 | "protocol": "TCP"
42 | }
43 | ],
44 | "resources": {},
45 | "volumeMounts": [
46 | {
47 | "name": "default-token-mbjjj",
48 | "readOnly": true,
49 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
50 | }
51 | ],
52 | "terminationMessagePath": "/dev/termination-log",
53 | "terminationMessagePolicy": "File",
54 | "imagePullPolicy": "IfNotPresent"
55 | }
56 | ],
57 | "restartPolicy": "Always",
58 | "terminationGracePeriodSeconds": 30,
59 | "dnsPolicy": "ClusterFirst",
60 | "serviceAccountName": "default",
61 | "serviceAccount": "default",
62 | "nodeName": "liqo-test",
63 | "securityContext": {},
64 | "schedulerName": "default-scheduler",
65 | "tolerations": [
66 | {
67 | "key": "virtual-node.liqo.io/not-allowed",
68 | "operator": "Exists",
69 | "effect": "NoExecute"
70 | },
71 | {
72 | "key": "node.kubernetes.io/not-ready",
73 | "operator": "Exists",
74 | "effect": "NoExecute",
75 | "tolerationSeconds": 300
76 | },
77 | {
78 | "key": "node.kubernetes.io/unreachable",
79 | "operator": "Exists",
80 | "effect": "NoExecute",
81 | "tolerationSeconds": 300
82 | }
83 | ],
84 | "priority": 0,
85 | "enableServiceLinks": true
86 | },
87 | "status": {
88 | "phase": "Running"
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/__mocks__/pod_logs.txt:
--------------------------------------------------------------------------------
1 | [INFO] plugin/ready: Still waiting on: "kubernetes"
2 | .:53
3 | [INFO] plugin/reload: Running configuration MD5 = 4e235fcc3696966e76816bcd9034ebc7
4 | CoreDNS-1.6.7
5 | linux/amd64, go1.13.6, da7f65b
6 | [INFO] plugin/ready: Still waiting on: "kubernetes"
7 | [INFO] plugin/ready: Still waiting on: "kubernetes"
8 | I1014 08:53:50.545770 1 trace.go:116] Trace[2019727887]: "Reflector ListAndWatch" name:pkg/mod/k8s.io/client-go@v0.17.2/tools/cache/reflector.go:105 (started: 2020-10-14 08:53:20.5452 +0000 UTC m=+0.019370001) (total time: 30.000433s):
9 | Trace[2019727887]: [30.000433s] [30.000433s] END
10 | E1014 08:53:50.545814 1 reflector.go:153] pkg/mod/k8s.io/client-go@v0.17.2/tools/cache/reflector.go:105: Failed to list *v1.Namespace: Get https://10.96.0.1:443/api/v1/namespaces?limit=500&resourceVersion=0: dial tcp 10.96.0.1:443: i/o timeout
11 | I1014 08:53:50.545832 1 trace.go:116] Trace[1427131847]: "Reflector ListAndWatch" name:pkg/mod/k8s.io/client-go@v0.17.2/tools/cache/reflector.go:105 (started: 2020-10-14 08:53:20.5452622 +0000 UTC m=+0.019432101) (total time: 30.0003768s):
12 | Trace[1427131847]: [30.0003768s] [30.0003768s] END
13 | E1014 08:53:50.545858 1 reflector.go:153] pkg/mod/k8s.io/client-go@v0.17.2/tools/cache/reflector.go:105: Failed to list *v1.Service: Get https://10.96.0.1:443/api/v1/services?limit=500&resourceVersion=0: dial tcp 10.96.0.1:443: i/o timeout
14 | I1014 08:53:50.545897 1 trace.go:116] Trace[939984059]: "Reflector ListAndWatch" name:pkg/mod/k8s.io/client-go@v0.17.2/tools/cache/reflector.go:105 (started: 2020-10-14 08:53:20.5452473 +0000 UTC m=+0.019417301) (total time: 30.0004771s):
15 | Trace[939984059]: [30.0004771s] [30.0004771s] END
16 | E1014 08:53:50.545922 1 reflector.go:153] pkg/mod/k8s.io/client-go@v0.17.2/tools/cache/reflector.go:105: Failed to list *v1.Endpoints: Get https://10.96.0.1:443/api/v1/endpoints?limit=500&resourceVersion=0: dial tcp 10.96.0.1:443: i/o timeout
17 | [INFO] plugin/ready: Still waiting on: "kubernetes"
18 |
--------------------------------------------------------------------------------
/__mocks__/pods_metrics.json:
--------------------------------------------------------------------------------
1 | {
2 | "podMetrics": [
3 | {
4 | "kind": "PodMetrics",
5 | "apiVersion": "metrics.k8s.io/v1beta1",
6 | "metadata": {
7 | "name": "hello-world-deployment-6756549f5-x66v9",
8 | "namespace": "test-66b8128a-3fdf-4d78-bd8f-40b7ef860a8b"
9 | },
10 | "containers": [
11 | {
12 | "name": "hello-world",
13 | "usage": {
14 | "cpu": "1",
15 | "memory": "5065970Ki"
16 | }
17 | }
18 | ]
19 | },
20 | {
21 | "kind": "PodMetrics",
22 | "apiVersion": "metrics.k8s.io/v1beta1",
23 | "metadata": {
24 | "name": "hello-world-deployment-6756549f5-c7qzv",
25 | "namespace": "test-66b8128a-3fdf-4d78-bd8f-40b7ef860a8b"
26 | },
27 | "containers": [
28 | {
29 | "name": "hello-world",
30 | "usage": {
31 | "cpu": "1191721438n",
32 | "memory": "7624Ki"
33 | }
34 | }
35 | ]
36 | },
37 | {
38 | "kind": "PodMetrics",
39 | "apiVersion": "metrics.k8s.io/v1beta1",
40 | "metadata": {
41 | "name": "hello-world-deployment-6756549f5-c7sx8",
42 | "namespace": "test-66b8128a-3fdf-4d78-bd8f-40b7ef860a8b"
43 | },
44 | "containers": [
45 | {
46 | "name": "hello-world",
47 | "usage": {
48 | "cpu": "3",
49 | "memory": "7624Ki"
50 | }
51 | }
52 | ]
53 | },
54 | {
55 | "kind": "PodMetrics",
56 | "apiVersion": "metrics.k8s.io/v1beta1",
57 | "metadata": {
58 | "name": "hello-world-deployment-6756549f5-x66v8",
59 | "namespace": "test-66b8128a-3fdf-4d78-bd8f-40b7ef860a8b"
60 | },
61 | "containers": [
62 | {
63 | "name": "hello-world",
64 | "usage": {
65 | "cpu": "3",
66 | "memory": "7624Ki"
67 | }
68 | }
69 | ]
70 | }
71 | ]
72 | }
73 |
--------------------------------------------------------------------------------
/__mocks__/scheduling.liqo.io.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "apiVersion": "v1",
4 | "groupVersion": "scheduling.liqo.io/v1alpha1",
5 | "resources": [
6 | {
7 | "name": "schedulingnodes",
8 | "singularName": "schedulingnode",
9 | "namespaced": false,
10 | "kind": "SchedulingNode",
11 | "verbs": [
12 | "delete",
13 | "deletecollection",
14 | "get",
15 | "list",
16 | "patch",
17 | "create",
18 | "update",
19 | "watch"
20 | ],
21 | "storageVersionHash": "igicX09+lSY="
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/__mocks__/searchdomain.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "discovery.liqo.io/v1",
3 | "items": [
4 | {
5 | "apiVersion": "discovery.liqo.io/v1",
6 | "kind": "SearchDomain",
7 | "metadata": {
8 | "name": "search-domain-test",
9 | "resourceVersion": "00000",
10 | "selfLink": "/apis/discovery.liqo.io/v1/searchdomains/search-domain-test"
11 | },
12 | "spec": {
13 | "autojoin": true,
14 | "domain": ".test"
15 | }
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/__mocks__/sharing.liqo.io.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "apiVersion": "v1",
4 | "groupVersion": "sharing.liqo.io/v1alpha1",
5 | "resources": [
6 | {
7 | "name": "advertisements",
8 | "singularName": "advertisement",
9 | "namespaced": false,
10 | "kind": "Advertisement",
11 | "verbs": [
12 | "delete",
13 | "deletecollection",
14 | "get",
15 | "list",
16 | "patch",
17 | "create",
18 | "update",
19 | "watch"
20 | ],
21 | "storageVersionHash": "SP0hABvyTEw="
22 | },
23 | {
24 | "name": "advertisements/status",
25 | "singularName": "",
26 | "namespaced": false,
27 | "kind": "Advertisement",
28 | "verbs": ["get", "patch", "update"]
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/__mocks__/tunnelendpoints.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "TunnelEndpoint",
7 | "metadata": {
8 | "name": "test-tunendpoint",
9 | "resourceVersion": "000000"
10 | },
11 | "spec": {
12 | "clusterID": "test",
13 | "podCIDR": "",
14 | "tunnelPrivateIP": "",
15 | "tunnelPublicIP": ""
16 | },
17 | "status": {
18 | "phase": "New",
19 | "remoteRemappedPodCIDR": ""
20 | }
21 | }
22 | ],
23 | "kind": "TunnelEndpointList",
24 | "metadata": {
25 | "continue": "",
26 | "resourceVersion": "0000000"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/__mocks__/views.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "View",
7 | "metadata": {
8 | "name": "awesome-view",
9 | "namespace": "default",
10 | "resourceVersion": "000000",
11 | "selfLink": "/apis/test/v1/namespaces/default/views/awesome-view"
12 | },
13 | "spec": {
14 | "viewName": "Liqo View",
15 | "enabled": true,
16 | "resources": [
17 | {
18 | "resourceName": "Advertisement",
19 | "resourcePath": "/apis/net.liqo.io/v1alpha1/advertisements.protocol.liqo.io"
20 | },
21 | {
22 | "resourceName": "Test2",
23 | "resourcePath": "/apis/net.liqo.io/v1alpha1/tunnelendpoints.liqonet.liqo.io"
24 | },
25 | {
26 | "resourceName": "PodsTest",
27 | "resourcePath": "/api/v1/pods"
28 | }
29 | ]
30 | }
31 | }
32 | ],
33 | "kind": "ViewList",
34 | "metadata": {
35 | "continue": "",
36 | "resourceVersion": "0000000"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/__mocks__/views_alt_template.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "View",
7 | "metadata": {
8 | "name": "awesome-view",
9 | "resourceVersion": "000001",
10 | "selfLink": "/apis/test/v1/namespaces/default/views/awesome-view"
11 | },
12 | "spec": {
13 | "viewName": "Liqo View",
14 | "enabled": true,
15 | "crds": [
16 | {
17 | "resourceName": "Test",
18 | "resourcePath": "/apis/net.liqo.io/v1alpha1/advertisements.protocol.liqo.io"
19 | },
20 | {
21 | "resourceName": "Test2",
22 | "resourcePath": "/apis/net.liqo.io/v1alpha1/tunnelendpoints.liqonet.liqo.io"
23 | }
24 | ]
25 | }
26 | }
27 | ],
28 | "kind": "ViewList",
29 | "metadata": {
30 | "continue": "",
31 | "resourceVersion": "0000001"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/__mocks__/views_another.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "View",
7 | "metadata": {
8 | "name": "test-custom-view",
9 | "resourceVersion": "000000",
10 | "selfLink": "/apis/test/v1/namespaces/default/views/awesome-view"
11 | },
12 | "spec": {
13 | "viewName": "Test Custom View",
14 | "enabled": true,
15 | "resources": [
16 | {
17 | "resourceName": "Advertisement",
18 | "resourcePath": "/apis/net.liqo.io/v1alpha1/advertisements.protocol.liqo.io"
19 | }
20 | ]
21 | }
22 | }
23 | ],
24 | "kind": "ViewList",
25 | "metadata": {
26 | "continue": "",
27 | "resourceVersion": "0000000"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/__mocks__/views_empty.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "View",
7 | "metadata": {
8 | "name": "awesome-view",
9 | "resourceVersion": "000000",
10 | "selfLink": "/apis/test/v1/namespaces/default/views/awesome-view"
11 | },
12 | "spec": {
13 | "viewName": "Liqo View",
14 | "enabled": true,
15 | "resources": []
16 | }
17 | }
18 | ],
19 | "kind": "ViewList",
20 | "metadata": {
21 | "continue": "",
22 | "resourceVersion": "0000000"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/__mocks__/views_modified.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "View",
7 | "metadata": {
8 | "name": "awesome-view",
9 | "resourceVersion": "000001",
10 | "selfLink": "/apis/test/v1/namespaces/default/views/awesome-view"
11 | },
12 | "spec": {
13 | "viewName": "Liqo View",
14 | "enabled": true,
15 | "resources": [
16 | {
17 | "resourceName": "Advertisement",
18 | "resourcePath": "/apis/net.liqo.io/v1alpha1/advertisements.protocol.liqo.io"
19 | },
20 | {
21 | "resourceName": "Test2",
22 | "resourcePath": "/apis/net.liqo.io/v1alpha1/tunnelendpoints.liqonet.liqo.io"
23 | },
24 | {
25 | "resourceName": "LiqoDashTest",
26 | "resourcePath": "/apis/net.liqo.io/v1alpha1/liqodashtests.dashboard.liqo.io"
27 | }
28 | ]
29 | }
30 | }
31 | ],
32 | "kind": "ViewList",
33 | "metadata": {
34 | "continue": "",
35 | "resourceVersion": "0000001"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/__mocks__/views_withLayout.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "test/v1",
3 | "items": [
4 | {
5 | "apiVersion": "test/v1",
6 | "kind": "View",
7 | "metadata": {
8 | "name": "awesome-view",
9 | "resourceVersion": "000000",
10 | "selfLink": "/apis/test/v1/namespaces/default/views/awesome-view"
11 | },
12 | "spec": {
13 | "enabled": true,
14 | "resources": [
15 | {
16 | "layout": {
17 | "lg": {
18 | "h": 10,
19 | "w": 10,
20 | "x": 0,
21 | "y": 0
22 | }
23 | },
24 | "resourceName": "Advertisement",
25 | "resourcePath": "/apis/net.liqo.io/v1alpha1/advertisements.protocol.liqo.io"
26 | }
27 | ]
28 | }
29 | }
30 | ],
31 | "kind": "ViewList",
32 | "metadata": {
33 | "continue": "",
34 | "resourceVersion": "0000000"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/__mocks__/virtualkubelet.liqo.io.json:
--------------------------------------------------------------------------------
1 | {
2 | "kind": "APIResourceList",
3 | "apiVersion": "v1",
4 | "groupVersion": "virtualkubelet.liqo.io/v1alpha1",
5 | "resources": [
6 | {
7 | "name": "namespacenattingtables",
8 | "singularName": "namespacenattingtable",
9 | "namespaced": false,
10 | "kind": "NamespaceNattingTable",
11 | "verbs": [
12 | "delete",
13 | "deletecollection",
14 | "get",
15 | "list",
16 | "patch",
17 | "create",
18 | "update",
19 | "watch"
20 | ],
21 | "storageVersionHash": "YJqJ3KNdQoc="
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-env']
3 | };
4 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -eu
2 |
3 | cat << EOF >/usr/share/nginx/html/config.js
4 | window.APISERVER_URL="${APISERVER_URL:-undefined}";
5 | window.OIDC_PROVIDER_URL="${OIDC_PROVIDER_URL:-undefined}";
6 | window.OIDC_CLIENT_ID="${OIDC_CLIENT_ID:-undefined}";
7 | window.OIDC_CLIENT_SECRET="${OIDC_CLIENT_SECRET:-undefined}";
8 | window.OIDC_REDIRECT_URI="${OIDC_REDIRECT_URI:-undefined}";
9 | EOF
10 |
11 | sed -i".old" 's#
##' /usr/share/nginx/html/index.html
12 |
13 | nginx -g "daemon off;"
14 |
--------------------------------------------------------------------------------
/docs/images/CRD-with-field-description.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/docs/images/CRD-with-field-description.png
--------------------------------------------------------------------------------
/docs/images/CRD-without-field-description.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/docs/images/CRD-without-field-description.png
--------------------------------------------------------------------------------
/docs/images/dash-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/docs/images/dash-logo.png
--------------------------------------------------------------------------------
/docs/images/dashboard-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/docs/images/dashboard-ui.png
--------------------------------------------------------------------------------
/docs/images/link-to-resource-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/docs/images/link-to-resource-new.png
--------------------------------------------------------------------------------
/docs/images/link-to-resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/docs/images/link-to-resource.png
--------------------------------------------------------------------------------
/kubernetes/dashboard_chart/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/kubernetes/dashboard_chart/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: liqodash_chart
3 | description: A Helm chart for Liqo Dashboard
4 |
5 | # A chart can be either an 'application' or a 'library' chart.
6 | #
7 | # Application charts are a collection of templates that can be packaged into versioned archives
8 | # to be deployed.
9 | #
10 | # Library charts provide useful utilities or functions for the chart developer. They're included as
11 | # a dependency of application charts to inject those utilities and functions into the rendering
12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed.
13 | type: application
14 |
15 | # This is the chart version. This version number should be incremented each time you make changes
16 | # to the chart and its templates, including the app version.
17 | # Versions are expected to follow Semantic Versioning (https://semver.org/)
18 | version: 0.1.0
19 |
20 | # This is the version number of the application being deployed. This version number should be
21 | # incremented each time you make changes to the application. Versions are not expected to
22 | # follow Semantic Versioning. They should reflect the version the application is using.
23 | appVersion: 1.16.0
24 |
--------------------------------------------------------------------------------
/kubernetes/dashboard_chart/README.md:
--------------------------------------------------------------------------------
1 | dashboard_chart
2 | ==========
3 | A Helm chart for Liqo Dashboard
4 |
5 | Current chart version is `0.1.0`
6 |
7 | ## Chart Values
8 |
9 | | Key | Type | Default | Description |
10 | |-----|------|---------|-------------|
11 | | version | string | `"latest"` | The version of the dashboard to install. |
12 | | ingress | string | `` | The hostname used to configure an ingress for the dashboard. |
13 | | namespace | string | `"default"` | The namespace in which the dashboard will be installed. |
14 |
--------------------------------------------------------------------------------
/kubernetes/dashboard_chart/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for liqodash_chart.
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | image:
6 | repository: liqo/dashboard
7 | pullPolicy: Always
8 | # Overrides the image tag whose default is the chart appVersion.
9 |
10 | suffix: ""
11 | version: "latest"
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 8443 ssl http2;
3 | server_name localhost;
4 | http2_recv_timeout 3600s;
5 | proxy_buffering off;
6 | ssl_certificate /etc/nginx/ssl/nginx.cert;
7 | ssl_certificate_key /etc/nginx/ssl/nginx.key;
8 | ssl_protocols TLSv1.2 TLSv1.3;
9 | ssl_ciphers HIGH:!aNULL:!MD5;
10 | #charset koi8-r;
11 | #access_log /var/log/nginx/host.access.log main;
12 |
13 | location / {
14 | root /usr/share/nginx/html;
15 | index index.html index.htm;
16 | try_files $uri /index.html;
17 | }
18 |
19 | location /healthz {
20 | access_log off;
21 | return 200 "healthy\n";
22 | }
23 |
24 | location /apiserver {
25 | proxy_set_header Authorization $http_authorization;
26 | proxy_read_timeout 3600s;
27 |
28 | if ($request_method = 'OPTIONS') {
29 | add_header 'Access-Control-Allow-Origin' '*';
30 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
31 | #
32 | # Custom headers and headers various browsers *should* be OK with but aren't
33 | #
34 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
35 | #
36 | # Tell client that this pre-flight info is valid for 20 days
37 | #
38 | add_header 'Access-Control-Max-Age' 1728000;
39 | add_header 'Content-Type' 'text/plain; charset=utf-8';
40 | add_header 'Content-Length' 0;
41 | return 204;
42 | }
43 | if ($request_method = 'POST') {
44 | add_header 'Access-Control-Allow-Origin' '*';
45 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
46 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
47 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
48 | }
49 | if ($request_method = 'GET') {
50 | add_header 'Access-Control-Allow-Origin' '*';
51 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
52 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
53 | add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
54 | }
55 |
56 | rewrite /apiserver/(.*) /$1 break;
57 | proxy_pass https://kubernetes.default.svc.cluster.local;
58 | proxy_redirect off;
59 | proxy_set_header Host $host;
60 | }
61 |
62 | #error_page 404 /404.html;
63 |
64 | # redirect server error pages to the static page /50x.html
65 | #
66 | error_page 500 502 503 504 /50x.html;
67 | location = /50x.html {
68 | root /usr/share/nginx/html;
69 | }
70 |
71 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80
72 | #
73 | #location ~ \.php$ {
74 | # proxy_pass http://127.0.0.1;
75 | #}
76 |
77 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
78 | #
79 | #location ~ \.php$ {
80 | # root html;
81 | # fastcgi_pass 127.0.0.1:9000;
82 | # fastcgi_index index.php;
83 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
84 | # include fastcgi_params;
85 | #}
86 |
87 | # deny access to .htaccess files, if Apache's document root
88 | # concurs with nginx's one
89 | #
90 | #location ~ /\.ht {
91 | # deny all;
92 | #}
93 | }
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "start": "webpack-dev-server --config webpack.config.js",
5 | "build": "webpack -p --config webpack.config.js --display-error-details",
6 | "prettify-all": "node_modules/.bin/prettier --write **/*.{js,json,css}",
7 | "lint-check": "node_modules/.bin/prettier --check **/*.{js,json,css}",
8 | "test": "jest --coverage --silent --bail",
9 | "postinstall": "husky install"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.12.10",
13 | "@babel/plugin-proposal-class-properties": "^7.12.1",
14 | "@babel/plugin-proposal-object-rest-spread": "^7.12.1",
15 | "@babel/plugin-transform-react-jsx": "^7.12.12",
16 | "@babel/plugin-transform-runtime": "^7.12.10",
17 | "@babel/polyfill": "^7.12.1",
18 | "@babel/preset-env": "^7.12.11",
19 | "@babel/preset-react": "^7.12.10",
20 | "@testing-library/dom": "^7.29.1",
21 | "@testing-library/jest-dom": "^5.11.8",
22 | "@testing-library/react": "^11.2.2",
23 | "@testing-library/user-event": "^12.6.0",
24 | "babel-jest": "^26.6.3",
25 | "babel-loader": "^8.2.2",
26 | "babel-plugin-import": "^1.13.3",
27 | "css-loader": "^5.0.1",
28 | "file-loader": "^6.2.0",
29 | "html-webpack-plugin": "^4.5.1",
30 | "husky": "^5.0.9",
31 | "identity-obj-proxy": "^3.0.0",
32 | "jest-canvas-mock": "^2.3.0",
33 | "jest-fetch-mock": "^3.0.3",
34 | "jest-localstorage-mock": "^2.4.6",
35 | "less-loader": "6.2.0",
36 | "lint-staged": "^10.5.4",
37 | "prettier": "^2.2.1",
38 | "react": "^17.0.1",
39 | "react-bootstrap": "^1.4.0",
40 | "react-dom": "^17.0.1",
41 | "react-graph-vis": "^1.0.5",
42 | "react-router-dom": "^5.2.0",
43 | "robotstxt-webpack-plugin": "^7.0.0",
44 | "style-loader": "^2.0.0",
45 | "url-loader": "^4.1.1",
46 | "vis-network": "^8.5.6",
47 | "webpack": "4.44.1",
48 | "webpack-cli": "3.3.12",
49 | "webpack-dev-server": "3.11.0"
50 | },
51 | "dependencies": {
52 | "@ant-design/dark-theme": "^2.0.2",
53 | "@ant-design/icons": "^4.3.0",
54 | "@kubernetes/client-node": "git+https://github.com/LiqoTech/kubernetes-client-javascript.git#browser",
55 | "@openapi-contrib/openapi-schema-to-json-schema": "^3.0.4",
56 | "@rjsf/antd": "^2.4.1",
57 | "@rjsf/core": "^2.4.1",
58 | "ace-builds": "^1.4.12",
59 | "antd": "^4.10.0",
60 | "antd-dayjs-webpack-plugin": "^1.0.4",
61 | "antd-theme-generator": "^1.2.8",
62 | "antd-theme-webpack-plugin": "^1.3.7",
63 | "bizcharts": "4.0.14",
64 | "csvtojson": "^2.0.10",
65 | "dayjs": "^1.10.1",
66 | "file-saver": "^2.0.5",
67 | "generate-schema": "^2.6.0",
68 | "jest": "^26.6.3",
69 | "js-cookie": "^2.2.1",
70 | "lodash": "^4.17.20",
71 | "oidc-client": "^1.10.1",
72 | "react-ace": "^9.2.1",
73 | "react-color": "^2.19.3",
74 | "react-css-theme-switcher": "^0.2.2",
75 | "react-drag-listview": "^0.1.8",
76 | "react-github-btn": "^1.2.0",
77 | "react-grid-layout": "^1.2.0",
78 | "react-measure": "^2.5.2",
79 | "react-resizable": "^1.11.0",
80 | "react-resize-detector": "^5.2.0",
81 | "yaml": "^1.10.0"
82 | },
83 | "prettier": {
84 | "printWidth": 80,
85 | "trailingComma": "none",
86 | "tabWidth": 2,
87 | "semi": true,
88 | "singleQuote": true,
89 | "arrowParens": "avoid",
90 | "endOfLine": "lf"
91 | },
92 | "lint-staged": {
93 | "*.{js,json,css}": [
94 | "prettier --write"
95 | ]
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/app/App.css:
--------------------------------------------------------------------------------
1 | .app-content {
2 | margin: 84px 20px 20px;
3 | flex: 1 0 auto;
4 | }
5 |
6 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow {
7 | top: 14px;
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/CR.css:
--------------------------------------------------------------------------------
1 | .crd-choices {
2 | margin-top: 5px;
3 | margin-bottom: 15px;
4 | }
5 |
6 | .rep-container {
7 | margin: auto;
8 | }
9 |
10 | .cr-header {
11 | border: 1px solid rgb(235, 237, 240);
12 | }
13 |
14 | .cr-header .ant-page-header-heading-left {
15 | width: 60%;
16 | }
17 |
18 | .ant-collapse .ant-collapse-item .ant-collapse-header {
19 | position: relative !important;
20 | padding: 8px 16px 8px 40px !important;
21 | line-height: 1.5715 !important;
22 | cursor: pointer !important;
23 | -webkit-transition: all 0.3s !important;
24 | transition: all 0.3s !important;
25 | }
26 |
27 | .ant-collapse .ant-collapse-item .ant-collapse-header .ant-collapse-arrow {
28 | padding: 6px 16px 6px 0 !important;
29 | top: inherit !important;
30 | }
31 |
32 | .ant-collapse-content .ant-collapse-content-box {
33 | padding: 8px 12px !important;
34 | }
35 |
36 | .react-resizable {
37 | position: relative;
38 | background-clip: padding-box;
39 | }
40 |
41 | .react-resizable-handle {
42 | position: absolute;
43 | width: 10px;
44 | height: 100%;
45 | bottom: 0;
46 | right: -5px;
47 | cursor: col-resize;
48 | z-index: 1;
49 | }
50 |
51 | .dragHandler:hover {
52 | cursor: move;
53 | background-color: #ccc;
54 | }
55 |
--------------------------------------------------------------------------------
/src/app/CRD.css:
--------------------------------------------------------------------------------
1 | .crd-content {
2 | margin-bottom: 30px;
3 | padding: 20px 15px 20px 15px;
4 | letter-spacing: 0.01em;
5 | box-shadow: 0 2px 2px 0 #ececec, 0 0 0 1px #ececec;
6 | background: white;
7 | }
8 |
9 | .crd-choices {
10 | margin-top: 5px;
11 | margin-bottom: 15px;
12 | }
13 |
14 | .ant-typography ul,
15 | .ant-typography ol {
16 | margin: 0 0 0 0 !important;
17 | padding: 0 !important;
18 | }
19 |
20 | .ant-typography ul li,
21 | .ant-typography ol li {
22 | margin: 0 0 0 10px !important;
23 | padding: 0 0 0 4px !important;
24 | }
25 |
26 | .crd-container {
27 | overflow: hidden;
28 | width: 100%;
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/CRDList.css:
--------------------------------------------------------------------------------
1 | .no-crds-found {
2 | font-size: 20px;
3 | text-align: center;
4 | padding: 20px;
5 | }
6 |
7 | .crds-container {
8 | max-width: 80%;
9 | margin: 0 auto 0;
10 | }
11 |
12 | .ant-table-filter-trigger-container {
13 | width: 3em !important;
14 | right: unset !important;
15 | }
16 |
17 | .ant-table-filter-trigger {
18 | width: 3.5em !important;
19 | }
20 |
--------------------------------------------------------------------------------
/src/assets/database.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/src/assets/database.png
--------------------------------------------------------------------------------
/src/assets/logo_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/src/assets/logo_4.png
--------------------------------------------------------------------------------
/src/assets/name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/src/assets/name.png
--------------------------------------------------------------------------------
/src/common/AppFooter.css:
--------------------------------------------------------------------------------
1 | .app-footer {
2 | flex-shrink: 0;
3 | padding: 14px;
4 | }
5 |
--------------------------------------------------------------------------------
/src/common/AppFooter.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { withRouter } from 'react-router-dom';
3 | import { Layout, Row, Col, Typography } from 'antd';
4 | const Footer = Layout.Footer;
5 | import './AppFooter.css';
6 | import _ from 'lodash';
7 | import GitHubButton from 'react-github-btn';
8 |
9 | function AppFooter() {
10 | const [, setConfig] = useState(window.api.dashConfigs.current);
11 |
12 | useEffect(() => {
13 | window.api.DCArrayCallback.current.push(() =>
14 | setConfig(window.api.dashConfigs.current)
15 | );
16 | }, []);
17 |
18 | if (
19 | !_.isEmpty(window.api.dashConfigs.current) &&
20 | window.api.dashConfigs.current.spec.footer &&
21 | window.api.dashConfigs.current.spec.footer.enabled
22 | ) {
23 | const footer = window.api.dashConfigs.current.spec.footer;
24 | return (
25 |
49 | );
50 | } else return ;
51 | }
52 |
53 | export default withRouter(AppFooter);
54 |
--------------------------------------------------------------------------------
/src/common/AppHeader.css:
--------------------------------------------------------------------------------
1 | .app-header {
2 | position: fixed;
3 | width: 100%;
4 | z-index: 10;
5 | padding: 0 !important;
6 | }
7 |
8 | .app-menu {
9 | position: fixed;
10 | right: 0;
11 | z-index: 11;
12 | }
13 |
14 | .app-menu > li {
15 | padding: 0 20px;
16 | }
17 |
18 | .app-menu > li > a {
19 | padding: 0 20px;
20 | margin: 0 -20px;
21 | }
22 |
23 | .app-menu > li > a > i {
24 | margin-right: 0 !important;
25 | }
26 |
27 | .nav-icon {
28 | font-size: 30px;
29 | }
30 |
31 | @media (max-width: 768px) {
32 | .app-menu > li {
33 | padding: 0 15px;
34 | }
35 |
36 | .app-menu > li > a {
37 | padding: 0 15px;
38 | margin: 0 -15px;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/common/DraggableWrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function DraggableWrapper(props) {
4 | return (
5 |
6 | {props.children}
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/common/LoadingIndicator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Spin } from 'antd';
3 | import Icon from 'antd/lib/icon';
4 |
5 | export default function LoadingIndicator(props) {
6 | return (
7 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/common/NamespaceSelect.js:
--------------------------------------------------------------------------------
1 | import { SelectOutlined } from '@ant-design/icons';
2 | import { Col, Row, Select } from 'antd';
3 | import React, { useEffect, useRef, useState } from 'react';
4 | import Utils from '../services/Utils';
5 |
6 | export default function NamespaceSelect(props) {
7 | const [selectedNS, setSelectedNS] = useState('');
8 | const NSOptions = useRef([]);
9 | const NSs = useRef([]);
10 |
11 | useEffect(() => {
12 | if (Utils().parseJWT() && Utils().parseJWT().namespace) {
13 | const ns = Utils().parseJWT().namespace[0];
14 | NSOptions.current.push(
15 |
16 | );
17 | setSelectedNS(ns);
18 | }
19 |
20 | window.api
21 | .getNamespaces()
22 | .then(res => {
23 | NSs.current = res.body.items;
24 |
25 | NSs.current.forEach(NS =>
26 | NSOptions.current.push(
27 |
32 | )
33 | );
34 |
35 | if (props.defaultNS) {
36 | setSelectedNS(props.defaultNS);
37 | } else {
38 | NSOptions.current.push(
39 |
44 | );
45 |
46 | handleExternalChangeNS();
47 | window.api.NSArrayCallback.current.push(handleExternalChangeNS);
48 | }
49 | })
50 | .catch(error => console.error(error));
51 | }, []);
52 |
53 | const handleExternalChangeNS = () => {
54 | if (!window.api.namespace.current) setSelectedNS('all namespaces');
55 | else setSelectedNS(window.api.namespace.current);
56 | };
57 |
58 | const handleChangeNS = item => {
59 | setSelectedNS(item);
60 |
61 | if (props.handleChangeNS) props.handleChangeNS(item);
62 | else window.api.setNamespace(item);
63 | };
64 |
65 | return (
66 |
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/src/common/ResourceAutocomplete.js:
--------------------------------------------------------------------------------
1 | import { Select } from 'antd';
2 | import React, { useEffect, useState } from 'react';
3 | import Utils from '../services/Utils';
4 |
5 | export function ResourceAutocomplete(props) {
6 | const [autocomplete, setAutocomplete] = useState([]);
7 |
8 | const autoCompleteSearch = () => {
9 | window.api
10 | .getApis('/')
11 | .then(res => {
12 | res.body.groups.forEach(group => {
13 | group.versions.forEach(version => {
14 | window.api
15 | .getGenericResource('/apis/' + version.groupVersion)
16 | .then(_res => {
17 | let tempRes = [];
18 | _res.resources.forEach(resource => {
19 | if (resource.name.split('/').length === 1)
20 | tempRes.push({
21 | value:
22 | '/apis/' + version.groupVersion + '/' + resource.name,
23 | label: resource.name
24 | });
25 | });
26 | setAutocomplete(prev => {
27 | if (
28 | !prev.find(item =>
29 | Utils().arraysEqual(item.options, tempRes)
30 | )
31 | )
32 | return [
33 | ...prev,
34 | {
35 | label: version.groupVersion,
36 | options: tempRes
37 | }
38 | ];
39 | else return prev;
40 | });
41 | })
42 | .catch(error => console.error(error));
43 | });
44 | });
45 | })
46 | .catch(error => console.error(error));
47 |
48 | window.api
49 | .getGenericResource('/api/v1')
50 | .then(_res => {
51 | let tempRes = [];
52 | _res.resources.forEach(resource => {
53 | if (resource.name.split('/').length === 1)
54 | tempRes.push({
55 | value: '/api/v1/' + resource.name,
56 | label: resource.name
57 | });
58 | });
59 | setAutocomplete(prev => {
60 | if (!prev.find(item => Utils().arraysEqual(item.options, tempRes)))
61 | return [
62 | ...prev,
63 | {
64 | label: 'api',
65 | options: tempRes
66 | }
67 | ];
68 | else return prev;
69 | });
70 | })
71 | .catch(error => console.error(error));
72 | };
73 |
74 | useEffect(() => {
75 | window.api.autoCompleteCallback.current.push(autoCompleteSearch);
76 | autoCompleteSearch();
77 |
78 | return () => {
79 | window.api.autoCompleteCallback.current = window.api.autoCompleteCallback.current.filter(
80 | cb => cb !== autoCompleteSearch
81 | );
82 | };
83 | }, []);
84 |
85 | return (
86 |
87 |
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/src/common/SideBar.css:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | height: calc(100vh - 46px);
3 | margin-bottom: 46px;
4 | position: sticky;
5 | overflow-y: auto;
6 | overflow-x: hidden;
7 | left: 0;
8 | top: 0;
9 | z-index: 11;
10 | box-shadow: 0 0 0 0 #303030;
11 | }
12 |
13 | .image {
14 | height: 40px;
15 | width: 40px;
16 | margin: 10px 16px 16px 14px;
17 | float: left;
18 | }
19 |
20 | .app-title {
21 | float: left;
22 | }
23 |
24 | .app-title a {
25 | text-decoration: none;
26 | line-height: 64px;
27 | font-size: 21px;
28 | display: inline-block;
29 | }
30 |
31 | .app-title a:hover {
32 | text-decoration: none;
33 | }
34 |
35 | .title {
36 | margin: 16px 16px 16px 0;
37 | }
38 |
39 | @media (max-width: 768px) {
40 | .app-title a {
41 | font-size: 20px;
42 | }
43 | }
44 |
45 | .ant-menu .ant-menu-submenu .ant-menu-submenu-title {
46 | padding-left: 16px !important;
47 | margin-top: 0 !important;
48 | margin-bottom: 0 !important;
49 | }
50 |
51 | .ant-menu .ant-menu-submenu .ant-menu-item {
52 | padding-left: 24px !important;
53 | margin-top: 0 !important;
54 | margin-bottom: 0 !important;
55 | }
56 |
57 | .ant-menu-vertical .ant-menu-item:not(:last-child),
58 | .ant-menu-vertical-left .ant-menu-item:not(:last-child),
59 | .ant-menu-vertical-right .ant-menu-item:not(:last-child),
60 | .ant-menu-inline .ant-menu-item:not(:last-child) {
61 | margin-top: 0 !important;
62 | margin-bottom: 0 !important;
63 | }
64 |
65 | .ant-menu-vertical .ant-menu-item,
66 | .ant-menu-vertical-left .ant-menu-item,
67 | .ant-menu-vertical-right .ant-menu-item,
68 | .ant-menu-inline .ant-menu-item,
69 | .ant-menu-vertical .ant-menu-submenu-title,
70 | .ant-menu-vertical-left .ant-menu-submenu-title,
71 | .ant-menu-vertical-right .ant-menu-submenu-title,
72 | .ant-menu-inline .ant-menu-submenu-title {
73 | margin-top: 0 !important;
74 | margin-bottom: 0 !important;
75 | }
76 |
--------------------------------------------------------------------------------
/src/constants/index.js:
--------------------------------------------------------------------------------
1 | export const APP_NAME = 'LiqoDash';
2 | export const LIQO_NAMESPACE = 'liqo';
3 | export const VERSION = 'v1alpha1';
4 | export const TEMPLATE_GROUP = 'dashboard.liqo.io';
5 | export const LIQO_LABEL_ENABLED = 'liqo.io/enabled=true';
6 | export const testTimeout = 25000;
7 | export const DashboardConfigCRD = {
8 | metadata: {
9 | name: 'dashboardconfigs.dashboard.liqo.io'
10 | },
11 | spec: {
12 | group: TEMPLATE_GROUP,
13 | version: VERSION,
14 | names: {
15 | kind: 'DashboardConfig',
16 | plural: 'dashboardconfigs'
17 | }
18 | }
19 | };
20 | export const CustomViewCRD = {
21 | metadata: {
22 | name: 'views.dashboard.liqo.io'
23 | },
24 | spec: {
25 | group: TEMPLATE_GROUP,
26 | version: VERSION,
27 | names: {
28 | kind: 'View',
29 | plural: 'views'
30 | }
31 | }
32 | };
33 | export const defaultConfig = {
34 | apiVersion: TEMPLATE_GROUP + '/' + VERSION,
35 | kind: 'DashboardConfig',
36 | metadata: { name: 'default-config' },
37 | spec: {
38 | default: true,
39 | enabled: true,
40 | footer: { enabled: false },
41 | sidebar: { enabled: true },
42 | header: {
43 | namespaceSelector: true,
44 | resourceSearch: true
45 | },
46 | resources: []
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/src/customView/CustomView.css:
--------------------------------------------------------------------------------
1 | .cv-content {
2 | max-width: 90%;
3 | margin: 20px auto 30px;
4 | padding: 20px 15px 20px 15px;
5 | letter-spacing: 0.01em;
6 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08);
7 | background: white;
8 | }
9 |
10 | .react-grid-layout {
11 | position: relative;
12 | transition: height 200ms ease;
13 | }
14 | .react-grid-item {
15 | transition: all 200ms ease;
16 | transition-property: left, top;
17 | }
18 | .react-grid-item.cssTransforms {
19 | transition-property: transform;
20 | }
21 | .react-grid-item.resizing {
22 | z-index: 1;
23 | will-change: width, height;
24 | }
25 |
26 | .react-grid-item.react-draggable-dragging {
27 | transition: none;
28 | z-index: 3;
29 | will-change: transform;
30 | }
31 |
32 | .react-grid-item.dropping {
33 | visibility: hidden;
34 | }
35 |
36 | .react-grid-item.react-grid-placeholder {
37 | margin-top: 10px;
38 | background: #1890ff;
39 | opacity: 0.2;
40 | transition-duration: 100ms;
41 | z-index: 2;
42 | -webkit-user-select: none;
43 | -moz-user-select: none;
44 | -ms-user-select: none;
45 | -o-user-select: none;
46 | user-select: none;
47 | }
48 |
49 | .react-grid-item > .react-resizable-handle {
50 | position: absolute;
51 | width: 20px;
52 | height: 20px;
53 | bottom: 0;
54 | right: 0;
55 | cursor: se-resize;
56 | z-index: 10;
57 | }
58 |
59 | .react-grid-item > .react-resizable-handle::after {
60 | content: '';
61 | position: absolute;
62 | right: 3px;
63 | bottom: 3px;
64 | width: 5px;
65 | height: 5px;
66 | }
67 |
68 | .react-resizable-hide > .react-resizable-handle {
69 | display: none;
70 | }
71 |
72 | .inner-crd {
73 | overflow: auto;
74 | height: 100%;
75 | }
76 |
77 | /* Hide scrollbar for Chrome, Safari and Opera */
78 | .inner-crd::-webkit-scrollbar {
79 | display: none;
80 | }
81 |
82 | /* Hide scrollbar for IE, Edge and Firefox */
83 | .inner-crd {
84 | -ms-overflow-style: none; /* IE and Edge */
85 | scrollbar-width: none; /* Firefox */
86 | overflow: auto;
87 | height: 100%;
88 | }
89 |
--------------------------------------------------------------------------------
/src/customView/CustomViewHeader.js:
--------------------------------------------------------------------------------
1 | import { Col, Row, Typography } from 'antd';
2 | import { LayoutOutlined } from '@ant-design/icons';
3 | import React from 'react';
4 | import CustomIcon from '../resources/common/CustomIcon';
5 |
6 | export default function CustomViewHeader(props) {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {props.customView.spec.viewName
18 | ? props.customView.spec.viewName
19 | : props.customView.metadata.name}
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/customView/CustomViewUtils.js:
--------------------------------------------------------------------------------
1 | import ReactResizeDetector from 'react-resize-detector';
2 | import React from 'react';
3 |
4 | export function onResize(
5 | _layout,
6 | oldLayoutItem,
7 | layoutItem,
8 | resources,
9 | setResources,
10 | layout,
11 | setLayout,
12 | newBr
13 | ) {
14 | if (!oldLayoutItem) return;
15 | let index = resources.indexOf(
16 | resources.find(item => {
17 | return item.metadata.name === layoutItem.i;
18 | })
19 | );
20 |
21 | /** When changing width */
22 | if (oldLayoutItem.w !== layoutItem.w) {
23 | resources[index].width = layoutItem.w;
24 | }
25 | /** When changing height */
26 | if (oldLayoutItem.h !== layoutItem.h) {
27 | resources[index].height = layoutItem.h;
28 | }
29 |
30 | setLayout(prev => {
31 | prev[newBr] = _layout;
32 | return { ...prev };
33 | });
34 | }
35 |
36 | export function resizeDetector(setWidth, setBreakpoint, brCustom) {
37 | return (
38 | {
43 | let brWidth = {
44 | lg: brCustom && brCustom['lg'] ? brCustom['lg'] : 1000,
45 | md: brCustom && brCustom['md'] ? brCustom['md'] : 796,
46 | sm: brCustom && brCustom['sm'] ? brCustom['sm'] : 568,
47 | xs: brCustom && brCustom['xs'] ? brCustom['xs'] : 280,
48 | xss: brCustom && brCustom['xss'] ? brCustom['xss'] : 0
49 | };
50 | let breakpoint;
51 | if (width > brWidth['lg']) breakpoint = 'lg';
52 | else if (width < brWidth['lg'] && width > brWidth['md'])
53 | breakpoint = 'md';
54 | else if (width < brWidth['md'] && width > brWidth['sm'])
55 | breakpoint = 'sm';
56 | else if (width < brWidth['sm'] && width > brWidth['xs'])
57 | breakpoint = 'xs';
58 | else if (width < brWidth['xs'] && width > brWidth['xss'])
59 | breakpoint = 'xss';
60 | setWidth(width);
61 | setBreakpoint(breakpoint);
62 | window.dispatchEvent(new Event('resize'));
63 | }}
64 | />
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/src/editors/CRD/DesignEditorCRD.css:
--------------------------------------------------------------------------------
1 | .rep-crd-content {
2 | padding: 20px 15px 20px 15px;
3 | letter-spacing: 0.01em;
4 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08);
5 | background: white;
6 | max-width: 80%;
7 | margin: 20px auto 0;
8 | }
9 |
--------------------------------------------------------------------------------
/src/editors/CRD/NewResource.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Tabs, message, Badge, Drawer } from 'antd';
3 | import FormGenerator from '../OAPIV3FormGenerator/FormGenerator';
4 | import Editor from '../Editor';
5 | import NewFromFile from './NewFromFile';
6 |
7 | function NewResource(props) {
8 | const onClick = item => {
9 | if (!item || !item.metadata || item.metadata.name === '') {
10 | message.error('Errors in the custom resource definition');
11 | return;
12 | }
13 |
14 | submit(item);
15 | };
16 |
17 | const submit = item => {
18 | let promise;
19 | let namespace;
20 |
21 | if (item.metadata.namespace) {
22 | namespace = item.metadata.namespace;
23 | }
24 |
25 | if (props.resource) {
26 | if (props.resource.spec.scope === 'Namespaced' && !namespace) {
27 | namespace = 'default';
28 | }
29 |
30 | promise = window.api.createCustomResource(
31 | props.resource.spec.group,
32 | props.resource.spec.version,
33 | namespace,
34 | props.resource.spec.names.plural,
35 | item
36 | );
37 | } else {
38 | /** A new general resource */
39 | promise = props.addFunction(item);
40 | }
41 |
42 | promise
43 | .then(() => {
44 | props.setShowCreate(false);
45 | })
46 | .catch(() => {
47 | message.error('Could not create the resource');
48 | });
49 | };
50 |
51 | return (
52 |
58 | }
59 | placement={'right'}
60 | visible={props.showCreate}
61 | onClose={() => {
62 | props.setShowCreate(false);
63 | }}
64 | width={window.innerWidth > 1400 ? 1200 : window.innerWidth - 200}
65 | destroyOnClose
66 | >
67 |
68 |
69 |
73 |
74 | {props.resource &&
75 | props.resource.spec.validation &&
76 | props.resource.spec.validation.openAPIV3Schema ? (
77 |
78 |
79 |
80 | ) : null}
81 |
82 |
83 |
84 |
85 |
86 | );
87 | }
88 |
89 | export default NewResource;
90 |
--------------------------------------------------------------------------------
/src/editors/CRD/UpdateCR.css:
--------------------------------------------------------------------------------
1 | .update-cr-content {
2 | padding: 20px 15px 20px 15px;
3 | letter-spacing: 0.01em;
4 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08);
5 | background: white;
6 | max-width: 50%;
7 | margin: 20px auto 0;
8 | }
9 |
--------------------------------------------------------------------------------
/src/editors/CRD/UpdateCR.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import './UpdateCR.css';
3 | import { Tabs, message, Alert, Badge, Drawer } from 'antd';
4 | import LoadingIndicator from '../../common/LoadingIndicator';
5 | import FormGenerator from '../OAPIV3FormGenerator/FormGenerator';
6 | import Editor from '../Editor';
7 |
8 | function UpdateCR(props) {
9 | const [loading, setLoading] = useState(false);
10 |
11 | const submit = item => {
12 | item = {
13 | spec: item
14 | };
15 |
16 | setLoading(true);
17 |
18 | let promise = window.api.updateCustomResource(
19 | props.CRD.spec.group,
20 | props.CRD.spec.version,
21 | props.CR.metadata.namespace,
22 | props.CRD.spec.names.plural,
23 | props.CR.metadata.name,
24 | item
25 | );
26 |
27 | promise
28 | .then(() => {
29 | setLoading(false);
30 | props.setShowUpdate(false);
31 | message.success('Resource updated');
32 | })
33 | .catch(() => {
34 | setLoading(false);
35 | message.error('Could not update the resource');
36 | });
37 | };
38 |
39 | return (
40 |
43 | }
44 | placement={'right'}
45 | visible={props.showUpdate}
46 | onClose={() => {
47 | props.setShowUpdate(false);
48 | }}
49 | width={window.innerWidth > 1400 ? 1200 : window.innerWidth - 200}
50 | >
51 | {!loading ? (
52 |
53 |
54 |
58 |
59 | {props.CRD.spec.validation &&
60 | props.CRD.spec.validation.openAPIV3Schema ? (
61 |
62 |
63 |
69 |
70 |
71 | ) : null}
72 |
73 | ) : null}
74 | {loading ? : null}
75 |
76 | );
77 | }
78 |
79 | export default UpdateCR;
80 |
--------------------------------------------------------------------------------
/src/editors/Editor.js:
--------------------------------------------------------------------------------
1 | import AceEditor from 'react-ace';
2 | import { Button, Card, message } from 'antd';
3 | import React, { useEffect, useState } from 'react';
4 | import YAML from 'yaml';
5 | import 'ace-builds/webpack-resolver';
6 | import 'ace-builds/src-noconflict/mode-json';
7 | import 'ace-builds/src-noconflict/mode-yaml';
8 | import 'ace-builds/src-noconflict/theme-dawn';
9 | import 'ace-builds/src-noconflict/theme-monokai';
10 |
11 | export default function Editor(props) {
12 | const initValue = { metadata: { name: '' } };
13 | const [value, setValue] = useState(JSON.stringify(initValue, null, 2));
14 | const [valueYAML, setValueYAML] = useState(YAML.stringify(initValue));
15 | const [currentTab, setCurrentTab] = useState('YAML');
16 |
17 | const onClick = () => {
18 | let item;
19 |
20 | if (currentTab === 'YAML') {
21 | try {
22 | item = YAML.parse(valueYAML);
23 | } catch (error) {
24 | message.error('YAML not valid');
25 | return;
26 | }
27 | } else {
28 | try {
29 | item = JSON.parse(value);
30 | } catch (error) {
31 | message.error('JSON not valid');
32 | return;
33 | }
34 | }
35 |
36 | props.onClick(item);
37 | };
38 |
39 | useEffect(() => {
40 | setValue(props.value ? props.value : JSON.stringify(initValue, null, 2));
41 | setValueYAML(
42 | props.value
43 | ? YAML.stringify(JSON.parse(props.value))
44 | : YAML.stringify(initValue)
45 | );
46 | }, [props.value]);
47 |
48 | const onChange = value => {
49 | if (currentTab === 'YAML') {
50 | setValueYAML(value);
51 | try {
52 | setValue(JSON.stringify(YAML.parse(value), null, 2));
53 | } catch {}
54 | } else {
55 | setValue(value);
56 | try {
57 | setValueYAML(YAML.stringify(JSON.parse(value)));
58 | } catch {}
59 | }
60 | };
61 |
62 | return (
63 |
64 |
{
82 | setCurrentTab(key);
83 | }}
84 | style={{ overflow: 'hidden' }}
85 | >
86 |
103 | {props.onClick ? (
104 |
105 |
108 |
109 | ) : null}
110 |
111 |
112 | );
113 | }
114 |
--------------------------------------------------------------------------------
/src/editors/OAPIV3FormGenerator/CustomField.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CustomSchemaField from './CustomSchemaField';
3 |
4 | export function CustomArrayFieldTemplate(props) {
5 | return (
6 |
7 | {props.items &&
8 | props.items.map(element => (
9 |
10 |
{element.children}
11 |
12 | ))}
13 |
14 | );
15 | }
16 |
17 | const CustomTitleField = function () {
18 | return ;
19 | };
20 |
21 | const CustomDescriptionFields = function () {
22 | return ;
23 | };
24 |
25 | export const fields = {
26 | TitleField: CustomTitleField,
27 | DescriptionField: CustomDescriptionFields
28 | };
29 |
30 | export const fieldsView = {
31 | TitleField: CustomTitleField,
32 | DescriptionField: CustomDescriptionFields,
33 | SchemaField: CustomSchemaField
34 | };
35 |
--------------------------------------------------------------------------------
/src/editors/OAPIV3FormGenerator/CustomFieldTemplateViewer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Popconfirm,
4 | Badge,
5 | Button,
6 | Col,
7 | Row,
8 | Tooltip,
9 | Typography,
10 | InputNumber
11 | } from 'antd';
12 | import {
13 | rootSplitCamelCaseAndUp,
14 | splitCamelCaseAndUp
15 | } from '../../services/stringUtils';
16 | import {
17 | QuestionCircleOutlined,
18 | EditOutlined,
19 | CloseOutlined
20 | } from '@ant-design/icons';
21 | import { customFieldTemplateGeneral } from './CustomFieldTemplate';
22 |
23 | function CustomFieldTemplateViewer(props) {
24 | const { id, classNames, label, required, errors, children } = props;
25 |
26 | let render = customFieldTemplateGeneral(props, true);
27 |
28 | if (!render) {
29 | return (
30 |
35 |
36 |
37 |
38 | {!props.disabled ? (
39 | <>
40 | {!required ? (
41 |
42 | ) : (
43 |
44 |
45 |
46 | )}
47 | >
48 | ) : (
49 |
50 | )}
51 | {label ? (
52 |
53 | {splitCamelCaseAndUp(label)}
54 |
55 | ) : (
56 |
57 | {rootSplitCamelCaseAndUp(id)}
58 |
59 | )}
60 | {props.schema.description ? (
61 |
62 |
63 |
64 | ) : null}
65 |
66 |
67 | {children}
68 |
69 | {props.disabled ? (
70 |
71 | }
73 | onClick={() => {
74 | props.onDisableChange();
75 | }}
76 | size={'small'}
77 | />
78 |
79 | ) : (
80 | {
84 | props.onDisableChange();
85 | }}
86 | okText="Yes"
87 | cancelText="No"
88 | >
89 | }
91 | htmlType={'submit'}
92 | size={'small'}
93 | />
94 |
95 | )}
96 |
97 |
98 | {errors}
99 |
100 | );
101 | } else {
102 | return render;
103 | }
104 | }
105 |
106 | export default CustomFieldTemplateViewer;
107 |
--------------------------------------------------------------------------------
/src/editors/OAPIV3FormGenerator/FormGenerator.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Utils from '../../services/Utils';
3 | import { withTheme } from '@rjsf/core';
4 | import { Theme as AntDTheme } from '@rjsf/antd';
5 | import { Button, Card, message, Typography } from 'antd';
6 | import { widgets } from './CustomWidget';
7 | import { fields } from './CustomField';
8 | import CustomFieldTemplate from './CustomFieldTemplate';
9 |
10 | const Form = withTheme(AntDTheme);
11 |
12 | function FormGenerator(props) {
13 | const util = Utils();
14 | let schema = util.OAPIV3toJSONSchema(
15 | props.CRD.spec.validation.openAPIV3Schema
16 | ).properties.spec;
17 | let [currentMetadata, setCurrentMetadata] = useState(() => {
18 | let formData = { name: '' };
19 | if (props.CRD.spec.scope !== 'Cluster') {
20 | formData.namespace = 'default';
21 | }
22 | return formData;
23 | });
24 | let [currentSpec, setCurrentSpec] = useState(() =>
25 | props.onUpdate ? props.CR.spec : null
26 | );
27 |
28 | const onSubmit = value => {
29 | if (!props.onUpdate) {
30 | let metadata = currentMetadata;
31 |
32 | if (!metadata || !metadata.name || metadata.name === '') {
33 | message.error('Please insert a valid name');
34 | return;
35 | }
36 |
37 | let item = {
38 | spec: value.formData,
39 | metadata: metadata,
40 | apiVersion: props.CRD.spec.group + '/' + props.CRD.spec.version,
41 | kind: props.CRD.spec.names.kind
42 | };
43 | props.submit(item);
44 | } else {
45 | props.submit(value.formData);
46 | }
47 | };
48 |
49 | let metadata = {
50 | type: 'object',
51 | properties: {
52 | name: {
53 | type: 'string',
54 | description:
55 | 'The name of the resource. It is a string that uniquely identifies this object within the current namespace (if the resource is namespaced).'
56 | }
57 | }
58 | };
59 |
60 | if (props.CRD.spec.scope !== 'Cluster') {
61 | metadata.properties.namespace = {
62 | type: 'string',
63 | description:
64 | "The namespace of the resource. A namespace is a DNS compatible label that objects are subdivided into. The default namespace is 'default'. "
65 | };
66 | }
67 |
68 | return (
69 |
70 | {!props.onUpdate ? (
71 |
Metadata}
75 | style={{ marginBottom: 10 }}
76 | >
77 |
89 |
90 | ) : null}
91 |
106 |
107 | );
108 | }
109 |
110 | export default FormGenerator;
111 |
--------------------------------------------------------------------------------
/src/error-handles/ErrorRedirect.css:
--------------------------------------------------------------------------------
1 | .error-red {
2 | max-width: 30vw;
3 | margin: 50px auto;
4 | padding: 40px;
5 | border: 1px solid #c8c8c8;
6 | text-align: center;
7 | }
8 |
9 | .error-red .title {
10 | font-size: 50px;
11 | letter-spacing: 10px;
12 | margin: 16px;
13 | }
14 |
15 | .error-red .desc {
16 | font-size: 20px;
17 | margin-bottom: 20px;
18 | }
19 |
20 | .go-back-btn {
21 | min-width: 160px;
22 | }
23 |
--------------------------------------------------------------------------------
/src/error-handles/ErrorRedirect.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Result, Button, Card } from 'antd';
3 | import './ErrorRedirect.css';
4 | import LogoutOutlined from '@ant-design/icons/lib/icons/LogoutOutlined';
5 | import { useHistory } from 'react-router-dom';
6 |
7 | export default function ErrorRedirect(props) {
8 | const [description, setDescription] = useState('');
9 | let history = useHistory();
10 |
11 | useEffect(() => {
12 | let desc = '';
13 | switch (props.match.params.statusCode.toString()) {
14 | case '401':
15 | desc = 'Forbidden, not valid authentication credentials';
16 | break;
17 | case '403':
18 | desc = 'You do not have the right permissions to access this resource';
19 | break;
20 | case '404':
21 | desc = 'This page do not exist';
22 | break;
23 | default:
24 | desc = 'An error occurred, please login again';
25 | }
26 | setDescription(desc);
27 | }, []);
28 |
29 | return (
30 |
31 | }
41 | onClick={() => {
42 | props.tokenLogout ? props.tokenLogout() : history.goBack();
43 | }}
44 | >
45 | {props.tokenLogout ? 'Logout' : 'Go Back'}
46 |
47 | }
48 | />
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | .container {
7 | max-width: 100%;
8 | margin-left: auto;
9 | margin-right: auto;
10 | padding-left: 15px;
11 | padding-right: 15px;
12 | }
13 |
14 | /* Hide scrollbar for Chrome, Safari and Opera */
15 | .scrollbar::-webkit-scrollbar {
16 | display: none;
17 | }
18 |
19 | /* Hide scrollbar for IE, Edge and Firefox */
20 | .scrollbar {
21 | -ms-overflow-style: none; /* IE and Edge */
22 | scrollbar-width: none; /* Firefox */
23 | overflow: auto;
24 | height: 100%;
25 | }
26 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './themes/light-theme.less';
3 | import ReactDOM from 'react-dom';
4 | import './index.css';
5 | import App from './app/App';
6 | import { BrowserRouter as Router } from 'react-router-dom';
7 | import dark from './themes/dark.json';
8 | import light from './themes/light.json';
9 |
10 | if (
11 | localStorage.getItem('theme') === 'dark' ||
12 | !localStorage.getItem('theme')
13 | ) {
14 | localStorage.setItem('theme', 'dark');
15 | window.less.modifyVars(dark);
16 | } else if (localStorage.getItem('theme') === 'light')
17 | window.less.modifyVars(light);
18 | else window.less.modifyVars(JSON.parse(localStorage.getItem('theme')));
19 |
20 | const main = document.createElement('div');
21 | document.body.appendChild(main);
22 | main.setAttribute('id', 'main');
23 | ReactDOM.render(
24 |
25 |
26 | ,
27 | main
28 | );
29 |
--------------------------------------------------------------------------------
/src/login/Login.css:
--------------------------------------------------------------------------------
1 | .login-container {
2 | max-width: 500px;
3 | margin: 50px auto 0;
4 | padding: 40px;
5 | text-align: center;
6 | }
7 |
8 | .login-form-button {
9 | margin-top: 30px;
10 | width: 100%;
11 | }
12 |
--------------------------------------------------------------------------------
/src/login/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Login.css';
3 | import Utils from '../services/Utils';
4 | import { Button, Card, Form, Input, Typography } from 'antd';
5 |
6 | function Login(props) {
7 | const onFinish = values => {
8 | props.func(values.token);
9 | };
10 |
11 | const validator = (rule, value) => {
12 | try {
13 | Utils().parseJWT(value);
14 | } catch {
15 | return Promise.reject('Token is not valid');
16 | }
17 | };
18 |
19 | return (
20 |
21 |
22 |
29 | LiqoDash Login
30 |
31 | validator(rule, value) }
38 | ]}
39 | >
40 |
44 |
45 |
46 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export default Login;
62 |
--------------------------------------------------------------------------------
/src/resources/common/CustomIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function CustomIcon(props) {
4 | const Icon = ({ type, ...rest }) => {
5 | const icons = require(`@ant-design/icons`);
6 | let Component = icons[type];
7 | if (!Component) Component = icons['CloseOutlined'];
8 | return ;
9 | };
10 |
11 | return (
12 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/resources/common/DashboardConfigUtils.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export const createNewConfig = (params, props, location) => {
4 | let path =
5 | '/' +
6 | location.pathname.split('/')[1] +
7 | '/' +
8 | (params.group ? params.group + '/' : '') +
9 | params.version +
10 | '/' +
11 | params.resource;
12 |
13 | return {
14 | resourcePath: path,
15 | resourceName: props.kind,
16 | favourite: false,
17 | render: {
18 | columns: [
19 | {
20 | columnTitle: 'Name',
21 | columnContent: 'param.metadata.name'
22 | },
23 | {
24 | columnTitle: 'Namespace',
25 | columnContent: 'param.metadata.namespace'
26 | }
27 | ],
28 | tabs: []
29 | }
30 | };
31 | };
32 |
33 | export function getResourceConfig(params, location) {
34 | if (!_.isEmpty(window.api.dashConfigs.current)) {
35 | if (!window.api.dashConfigs.current.spec.resources)
36 | window.api.dashConfigs.current.spec.resources = [];
37 | let conf = window.api.dashConfigs.current.spec.resources.find(resource => {
38 | return resource.resourcePath === location.pathname;
39 | });
40 | if (!conf) {
41 | let path =
42 | '/' +
43 | location.pathname.split('/')[1] +
44 | '/' +
45 | (params.group ? params.group + '/' : '') +
46 | params.version +
47 | '/' +
48 | params.resource;
49 |
50 | conf = window.api.dashConfigs.current.spec.resources.find(resource => {
51 | return resource.resourcePath === path;
52 | });
53 | if (!conf) return {};
54 | }
55 | return conf;
56 | }
57 | return {};
58 | }
59 |
60 | export function updateResourceConfig(tempResourceConfig, params, location) {
61 | let CRD = window.api.getCRDFromKind('DashboardConfig');
62 |
63 | /** The resource has a config, update it */
64 | let path =
65 | '/' +
66 | location.pathname.split('/')[1] +
67 | '/' +
68 | (params.group ? params.group + '/' : '') +
69 | params.version +
70 | '/' +
71 | params.resource;
72 |
73 | let index = window.api.dashConfigs.current.spec.resources.indexOf(
74 | window.api.dashConfigs.current.spec.resources.find(
75 | resource => resource.resourcePath === path
76 | )
77 | );
78 |
79 | if (index !== -1) {
80 | window.api.dashConfigs.current.spec.resources[index] = tempResourceConfig;
81 | } else {
82 | window.api.dashConfigs.current.spec.resources.push(tempResourceConfig);
83 | }
84 |
85 | window.api
86 | .updateCustomResource(
87 | CRD.spec.group,
88 | CRD.spec.version,
89 | undefined,
90 | CRD.spec.names.plural,
91 | window.api.dashConfigs.current.metadata.name,
92 | window.api.dashConfigs.current
93 | )
94 | .catch(error => console.error(error));
95 | }
96 |
--------------------------------------------------------------------------------
/src/resources/common/LayoutUtils.js:
--------------------------------------------------------------------------------
1 | function deleteUnused(item) {
2 | delete item.isDraggable;
3 | delete item.moved;
4 | delete item.static;
5 | delete item.isBounded;
6 | delete item.isResizable;
7 | delete item.maxH;
8 | delete item.maxW;
9 | delete item.minH;
10 | delete item.minW;
11 | return item;
12 | }
13 |
14 | export const pruneLayouts = l => {
15 | Object.keys(l).forEach(br => {
16 | l[br].forEach(item => {
17 | deleteUnused(item);
18 | });
19 | });
20 | return l;
21 | };
22 |
23 | export const pruneLayout = l => {
24 | l.forEach(item => {
25 | deleteUnused(item);
26 | });
27 | return l;
28 | };
29 |
--------------------------------------------------------------------------------
/src/resources/common/ResourceBreadcrumb.js:
--------------------------------------------------------------------------------
1 | import { Breadcrumb } from 'antd';
2 | import React from 'react';
3 | import { useParams, useHistory, useLocation } from 'react-router-dom';
4 | import { HomeOutlined } from '@ant-design/icons';
5 |
6 | export default function ResourceBreadcrumb(props) {
7 | let params = useParams();
8 | let history = useHistory();
9 | let location = useLocation();
10 | let breads = [];
11 |
12 | function onClickApi() {
13 | let path = '/' + location.pathname.split('/')[1];
14 | if (!params.group) path = path + '/' + params.version;
15 | history.push(path);
16 | }
17 |
18 | breads.push(
19 |
20 | {location.pathname.split('/')[1]}
21 |
22 | );
23 |
24 | function onClickGroup() {
25 | let path =
26 | '/' +
27 | location.pathname.split('/')[1] +
28 | '/' +
29 | params.group +
30 | '/' +
31 | params.version;
32 | history.push(path);
33 | }
34 |
35 | if (params.group) {
36 | breads.push(
37 |
38 | {params.group}
39 |
40 | );
41 | }
42 |
43 | function onClickResource() {
44 | let path =
45 | '/' +
46 | location.pathname.split('/')[1] +
47 | '/' +
48 | (params.group ? params.group + '/' : '') +
49 | params.version +
50 | '/' +
51 | params.resource;
52 | history.push(path);
53 | }
54 |
55 | if (params.resource) {
56 | breads.push(
57 |
58 | {params.resource}
59 |
60 | );
61 | }
62 |
63 | if (params.resourceName) {
64 | breads.push();
65 | }
66 |
67 | delete breads[breads.length - 1];
68 |
69 | return (
70 |
71 | history.push('/')}>
72 |
73 |
74 |
75 |
76 | {breads}
77 |
78 |
79 |
80 |
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/src/resources/common/buttons/FavouriteButton.css:
--------------------------------------------------------------------------------
1 | .favourite-star .ant-rate-star-second {
2 | color: #d9d9d9 !important;
3 | }
4 |
--------------------------------------------------------------------------------
/src/resources/common/buttons/FavouriteButton.js:
--------------------------------------------------------------------------------
1 | import { Rate } from 'antd';
2 | import React from 'react';
3 | import './FavouriteButton.css';
4 | import { useParams, useLocation } from 'react-router-dom';
5 | import {
6 | createNewConfig,
7 | getResourceConfig,
8 | updateResourceConfig
9 | } from '../DashboardConfigUtils';
10 | import _ from 'lodash';
11 |
12 | export default function FavouriteButton(props) {
13 | let params = useParams();
14 | let location = useLocation();
15 |
16 | /** Update DashboardConfig with the favourite resource */
17 | const handleClickResourceListFav = () => {
18 | if (!_.isEmpty(window.api.dashConfigs.current)) {
19 | let resourceConfig = getResourceConfig(params, location);
20 |
21 | if (!_.isEmpty(resourceConfig)) {
22 | /** A config for this resource exists, update it */
23 | resourceConfig.favourite = !resourceConfig.favourite;
24 | } else {
25 | /** A config for this resource does not exists, create one */
26 | resourceConfig = createNewConfig(params, props, location);
27 | resourceConfig.favourite = true;
28 | }
29 |
30 | updateResourceConfig(resourceConfig, params, location);
31 | }
32 | };
33 |
34 | /** Update Resource with the 'favourite' annotation */
35 | const handleClickResourceFav = () => {
36 | let resource = props.resourceList.find(item => {
37 | return item.metadata.name === props.resourceName;
38 | });
39 | if (
40 | !resource.metadata.annotations ||
41 | !resource.metadata.annotations.favourite
42 | ) {
43 | resource.metadata.annotations = { favourite: 'true' };
44 | } else {
45 | resource.metadata.annotations.favourite = null;
46 | }
47 | return window.api
48 | .updateGenericResource(resource.metadata.selfLink, resource)
49 | .catch(error => console.error(error));
50 | };
51 |
52 | return (
53 |
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/src/resources/common/buttons/IconButton.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import { Button, Input, List, Modal, Tooltip, Typography } from 'antd';
3 | import { splitCamelCaseAndUp } from '../../../services/stringUtils';
4 | import { SearchOutlined } from '@ant-design/icons';
5 | import { useParams } from 'react-router-dom';
6 | import CustomIcon from '../CustomIcon';
7 |
8 | export default function IconButton(props) {
9 | let params = useParams();
10 |
11 | const [icons, setIcons] = useState([]);
12 | const totalIcons = useRef([]);
13 |
14 | useEffect(() => {
15 | const iconsArray = [];
16 | const _icons = require(`@ant-design/icons`);
17 | for (let key in _icons) {
18 | if (_icons.hasOwnProperty(key)) {
19 | if (
20 | key !== 'setTwoToneColor' &&
21 | key !== 'getTwoToneColor' &&
22 | key !== 'createFromIconfontCN' &&
23 | key.includes('Outlined')
24 | ) {
25 | iconsArray.push({
26 | key: key,
27 | icon: (
28 |
29 |
48 | )
49 | });
50 | }
51 | }
52 | }
53 |
54 | setIcons(iconsArray);
55 | totalIcons.current = iconsArray;
56 | }, [params]);
57 |
58 | const onChange = value => {
59 | let searched = totalIcons.current.filter(icon =>
60 | icon.key.toUpperCase().includes(value.toUpperCase().replace(' ', ''))
61 | );
62 | setIcons(searched);
63 | };
64 |
65 | return (
66 | {
72 | setIcons(totalIcons.current);
73 | props.setOnAddIcon(false);
74 | }}
75 | destroyOnClose
76 | >
77 |
78 |
79 | }
83 | onChange={e => onChange(e.target.value)}
84 | />
85 |
86 |
{item.icon}
}
90 | pagination={{
91 | showSizeChanger: false,
92 | pageSize: 16
93 | }}
94 | />
95 |
96 |
97 | );
98 | }
99 |
--------------------------------------------------------------------------------
/src/resources/resource/CustomTab.css:
--------------------------------------------------------------------------------
1 | .custom-tab {
2 | letter-spacing: 0.01em;
3 | border: 1px solid #ececec;
4 | box-shadow: 2px 2px 2px 2px #ececec, 1px 1px 1px 1px #ececec;
5 | background: white;
6 | min-height: 100%;
7 | }
8 |
--------------------------------------------------------------------------------
/src/resources/resource/pod/Logs.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import AceEditor from 'react-ace';
3 | import 'ace-builds/webpack-resolver';
4 | import 'ace-builds/src-noconflict/mode-markdown';
5 | import 'ace-builds/src-noconflict/theme-monokai';
6 | import { useLocation } from 'react-router-dom';
7 |
8 | export default function Logs() {
9 | const [log, setLog] = useState('');
10 |
11 | let location = useLocation();
12 |
13 | useEffect(() => {
14 | window.api.getPodLogs(location.pathname).then(res => {
15 | setLog(res);
16 | });
17 | }, []);
18 |
19 | return (
20 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/src/resources/resourceList/columnContentFunction.js:
--------------------------------------------------------------------------------
1 | import Utils from '../../services/Utils';
2 | import React from 'react';
3 |
4 | export function columnContentFunction(resource, content) {
5 | let parameters = content.split('%//');
6 | if (parameters.length > 1) {
7 | let content = '';
8 | parameters.forEach(param => {
9 | if (param.slice(0, 6) === 'param.') {
10 | let object = Utils().index(resource, param.slice(6));
11 | if (typeof object === 'object' || Array.isArray(object))
12 | object = '[Object is not primary type]';
13 | content += object ? object : "''";
14 | } else {
15 | if (param.slice(0, 1) === "'" && param.slice(-1) === "'") {
16 | let string = param.slice(1, -1);
17 | content += string;
18 | }
19 | }
20 | });
21 | return content;
22 | } else {
23 | if (parameters[0].slice(0, 6) === 'param.')
24 | parameters[0] = parameters[0].slice(6);
25 | return Utils().index(resource, parameters[0]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/services/Colors.js:
--------------------------------------------------------------------------------
1 | export const colorsBasic = [
2 | '#6395f9',
3 | '#62daab',
4 | '#657798',
5 | '#f6c022',
6 | '#e96c5b',
7 | '#74cbed',
8 | '#9967bd',
9 | '#ff9d4e',
10 | '#F44336',
11 | '#e91e63',
12 | '#9c27b0',
13 | '#673ab7',
14 | '#ff9800',
15 | '#ff5722',
16 | '#795548',
17 | '#607d8b',
18 | '#3f51b5',
19 | '#2196F3',
20 | '#00bcd4',
21 | '#009688',
22 | '#2196F3',
23 | '#32c787',
24 | '#00BCD4',
25 | '#ff5652',
26 | '#ffc107',
27 | '#ff85af',
28 | '#FF9800',
29 | '#39bbb0',
30 | '#4CAF50',
31 | '#ffeb3b',
32 | '#ffc107',
33 | '#faad14',
34 | '#b0790d',
35 | '#f5222d',
36 | '#a5262c'
37 | ];
38 |
39 | export const colorsExtended = [
40 | '#bde8ff',
41 | '#9ec9ff',
42 | '#7eabff',
43 | '#5a8df8',
44 | '#7efac6',
45 | '#60ddab',
46 | '#40c190',
47 | '#14a577',
48 | '#d0e3ff',
49 | '#b4c7ec',
50 | '#99acd0',
51 | '#7f91b4',
52 | '#ffe244',
53 | '#f6bd16',
54 | '#d7a300',
55 | '#b98800',
56 | '#ffbb97',
57 | '#ff9f7c',
58 | '#ff8363',
59 | '#e8684b',
60 | '#5ab7da',
61 | '#3a9cbe',
62 | '#0a82a3',
63 | '#006889',
64 | '#fdd5ff',
65 | '#ddb6ff',
66 | '#be99f7',
67 | '#a27fda',
68 | '#ffd985',
69 | '#ffb462',
70 | '#f39343',
71 | '#d47829',
72 | '#8bf4f2',
73 | '#6ed8d6',
74 | '#50bcba',
75 | '#2fa19f',
76 | '#ffcffa',
77 | '#ffabd5',
78 | '#ef8bb4',
79 | '#d27099',
80 | '#dddddd',
81 | '#bebebe',
82 | '#a1a1a1',
83 | '#848484'
84 | ];
85 |
--------------------------------------------------------------------------------
/src/services/SaveUtils.js:
--------------------------------------------------------------------------------
1 | const fileSaver = require('file-saver');
2 |
3 | export const handleSave = (json, fileName) => {
4 | const file = new File([json], fileName);
5 | fileSaver.saveAs(file);
6 | };
7 |
--------------------------------------------------------------------------------
/src/services/TimeUtils.js:
--------------------------------------------------------------------------------
1 | export function calculateAge(timestamp) {
2 | const date = new Date();
3 | const date2 = new Date(timestamp);
4 | let diffTime = Math.abs(date - date2) / (1000 * 60 * 60 * 24 * 30);
5 | if (Math.floor(diffTime) === 0) {
6 | diffTime = Math.abs(date - date2) / (1000 * 60 * 60 * 24) - 1;
7 | if (Math.floor(diffTime) < 1) {
8 | diffTime = Math.abs(date - date2) / (1000 * 60 * 60);
9 | if (Math.floor(diffTime) === 0) {
10 | diffTime = Math.abs(date - date2) / (1000 * 60);
11 | if (Math.floor(diffTime) === 0) {
12 | diffTime = Math.abs(date - date2) / 1000;
13 | return Math.floor(diffTime) + 's';
14 | } else return Math.floor(diffTime) + 'm';
15 | } else return Math.floor(diffTime) + 'h';
16 | } else return Math.floor(diffTime + 1) + 'd';
17 | } else return Math.floor(diffTime) + 'M';
18 | }
19 |
20 | const ageReplacer = a => {
21 | const date = a.slice(-1);
22 | if (date === 's') return a.slice(0, -1) / (1000 * 60 * 60 * 24 * 30);
23 | else if (date === 'm') return a.slice(0, -1) / (60 * 60 * 24 * 30);
24 | else if (date === 'h') return a.slice(0, -1) / (60 * 24 * 30);
25 | else if (date === 'd') return a.slice(0, -1) / (24 * 30);
26 | return a.slice(0, -1);
27 | };
28 |
29 | export function compareAge(a, b) {
30 | a = ageReplacer(a);
31 | b = ageReplacer(b);
32 | return a - b;
33 | }
34 |
--------------------------------------------------------------------------------
/src/services/api/Authenticator.js:
--------------------------------------------------------------------------------
1 | import { UserManager } from 'oidc-client';
2 | import React from 'react';
3 |
4 | /**
5 | * Api class to manage authN
6 | */
7 |
8 | export default function Authenticator() {
9 | let manager = null;
10 |
11 | if (
12 | (window.OIDC_CLIENT_ID === undefined ||
13 | window.OIDC_CLIENT_ID === 'undefined') &&
14 | OIDC_CLIENT_ID !== undefined
15 | ) {
16 | window.OIDC_CLIENT_ID = OIDC_CLIENT_ID;
17 | }
18 |
19 | if (
20 | (window.OIDC_PROVIDER_URL === undefined ||
21 | window.OIDC_PROVIDER_URL === 'undefined') &&
22 | OIDC_PROVIDER_URL !== undefined
23 | ) {
24 | window.OIDC_PROVIDER_URL = OIDC_PROVIDER_URL;
25 | }
26 |
27 | if (
28 | (window.OIDC_CLIENT_SECRET === undefined ||
29 | window.OIDC_CLIENT_SECRET === 'undefined') &&
30 | OIDC_CLIENT_SECRET !== undefined
31 | ) {
32 | window.OIDC_CLIENT_SECRET = OIDC_CLIENT_SECRET;
33 | }
34 |
35 | if (
36 | (window.OIDC_REDIRECT_URI === undefined ||
37 | window.OIDC_REDIRECT_URI === 'undefined') &&
38 | OIDC_REDIRECT_URI !== undefined
39 | ) {
40 | window.OIDC_REDIRECT_URI = OIDC_REDIRECT_URI;
41 | }
42 |
43 | if (window.OIDC_PROVIDER_URL) {
44 | manager = new UserManager({
45 | automaticSilentRenew: true,
46 | response_type: 'code',
47 | filterProtocolClaims: true,
48 | scope: 'openid ',
49 | loadUserInfo: true,
50 | client_secret: window.OIDC_CLIENT_SECRET,
51 | authority: window.OIDC_PROVIDER_URL,
52 | client_id: window.OIDC_CLIENT_ID,
53 | redirect_uri: `${window.OIDC_REDIRECT_URI}/callback`,
54 | post_logout_redirect_uri: `${window.OIDC_REDIRECT_URI}/logout`
55 | });
56 | }
57 |
58 | /**
59 | * Function to perform the login.
60 | * It will automatically redirect you
61 | * @return {Promise}
62 | */
63 | const login = () => {
64 | if (manager) return manager.signinRedirect();
65 | };
66 |
67 | /**
68 | * Function to process response from the authN endpoint
69 | * @return {Promise}
70 | */
71 | const completeLogin = () => {
72 | if (manager)
73 | return manager
74 | .signinRedirectCallback()
75 | .catch(error => console.error(error));
76 | };
77 |
78 | /**
79 | * Function to logout
80 | * @return {Promise}
81 | */
82 | const logout = () => {
83 | if (manager)
84 | return manager.signoutRedirect().catch(error => console.error(error));
85 | };
86 |
87 | return {
88 | manager,
89 | login,
90 | completeLogin,
91 | logout
92 | };
93 | }
94 |
--------------------------------------------------------------------------------
/src/services/api/DefaultRoutes.js:
--------------------------------------------------------------------------------
1 | import { Route } from 'react-router-dom';
2 | import CustomViewLoader from '../../views/CustomViewLoader';
3 | import APIGroupList from '../../resources/APIGroup/APIGroupList';
4 | import APIResourceList from '../../resources/APIResourceList/APIResourceList';
5 | import ResourceList from '../../resources/resourceList/ResourceList';
6 | import ResourceGeneral from '../../resources/resource/ResourceGeneral';
7 | import React from 'react';
8 |
9 | export default function DefaultRoutes() {
10 | return [
11 | }
16 | />,
17 | }
22 | />,
23 | }
28 | />,
29 | }
34 | />,
35 | }
40 | />,
41 | }
46 | />,
47 | }
52 | />,
53 | }
58 | />,
59 | }
64 | />,
65 | }
70 | />,
71 | }
78 | />,
79 | }
84 | />
85 | ];
86 | }
87 |
--------------------------------------------------------------------------------
/src/services/api/__mocks__/DefaultRoutes.js:
--------------------------------------------------------------------------------
1 | import { Route } from 'react-router-dom';
2 | import Home from '../../../views/liqo/Home';
3 | import CRD from '../../../resources/CRD/CRD';
4 | import CustomViewLoader from '../../../views/CustomViewLoader';
5 | import APIGroupList from '../../../resources/APIGroup/APIGroupList';
6 | import APIResourceList from '../../../resources/APIResourceList/APIResourceList';
7 | import ResourceList from '../../../resources/resourceList/ResourceList';
8 | import ResourceGeneral from '../../../resources/resource/ResourceGeneral';
9 | import React from 'react';
10 | import ConfigView from '../../../views/configView/ConfigView';
11 |
12 | export default function DefaultRoutes() {
13 | return [
14 | } />,
15 | }
20 | />,
21 | }
26 | />,
27 | }
32 | />,
33 | }
38 | />,
39 | }
44 | />,
45 | }
50 | />,
51 | }
56 | />,
57 | }
62 | />,
63 | }
68 | />,
69 | }
74 | />,
75 | }
80 | />,
81 | }
88 | />,
89 | }
94 | />,
95 | }
100 | />
101 | ];
102 | }
103 |
--------------------------------------------------------------------------------
/src/services/stringUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Add space between camelCase text.
3 | */
4 | export function unCamelCase(str) {
5 | str = str.replace(/([a-z\xE0-\xFF])([A-Z\xC0\xDF])/g, '$1 $2');
6 | str = str.toLowerCase(); //add space between camelCase text
7 | return str;
8 | }
9 |
10 | /**
11 | * UPPERCASE first char of each word.
12 | */
13 | export function properCase(str) {
14 | return lowerCase(str).replace(/^\w|\s\w/g, upperCase);
15 | }
16 |
17 | /**
18 | * LOWERCASE and replace spaces with dashes
19 | */
20 | export function dashLowercase(str) {
21 | return lowerCase(str).replace(/\s+/g, '-');
22 | }
23 |
24 | /**
25 | * "Safer" String.toUpperCase()
26 | */
27 | export function upperCase(str) {
28 | return str.toUpperCase();
29 | }
30 |
31 | /**
32 | * "Safer" String.toLowerCase()
33 | */
34 | export function lowerCase(str) {
35 | return str.toLowerCase();
36 | }
37 |
38 | export function splitCamelCaseAndUp(str) {
39 | if (str) {
40 | str = unCamelCase(str);
41 | str = properCase(str);
42 | return str;
43 | }
44 | }
45 |
46 | export function rootSplitCamelCaseAndUp(str) {
47 | const array = str.split('_');
48 | str = splitCamelCaseAndUp(array[array.length - 2]);
49 | str = str + ' (' + array.pop() + ')';
50 | return str;
51 | }
52 |
53 | export const hashCode = s => {
54 | let hash = 0,
55 | i,
56 | chr;
57 | for (i = 0; i < s.length; i++) {
58 | chr = s.charCodeAt(i);
59 | hash = (hash << 5) - hash + chr;
60 | hash |= 0;
61 | }
62 | return Math.abs(hash);
63 | };
64 |
--------------------------------------------------------------------------------
/src/themes/ColorPicker.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { SketchPicker, ChromePicker } from 'react-color';
3 | import { Col, Input, Popover, Row } from 'antd';
4 | import { EditOutlined } from '@ant-design/icons';
5 |
6 | export default function ColorPicker(props) {
7 | const [color, setColor] = useState(props.color);
8 |
9 | const onChange = _color => {
10 | setColor(_color.hex);
11 | props.updateFunc(props.parameter, _color.hex);
12 | };
13 |
14 | const styles = {
15 | color: {
16 | width: '16px',
17 | height: '16px',
18 | borderRadius: '2px',
19 | background: color
20 | },
21 | swatch: {
22 | padding: '4px',
23 | background: '#fff',
24 | borderRadius: '2px',
25 | boxShadow: '0 0 0 1px rgba(0,0,0,.1)',
26 | display: 'inline-block',
27 | cursor: 'pointer'
28 | }
29 | };
30 |
31 | const content = (
32 |
33 |
34 |
35 | );
36 |
37 | return (
38 |
39 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/themes/ThemeUploader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { InboxOutlined } from '@ant-design/icons';
3 | import { Upload } from 'antd';
4 |
5 | export default function ThemeUploader(props) {
6 | const customRequest = info => {
7 | const reader = new FileReader();
8 | reader.onload = e => {
9 | const theme = JSON.parse(e.target.result.toString());
10 | window.less.modifyVars(theme).then(() => {
11 | localStorage.setItem('theme', e.target.result.toString());
12 | props.changeItems(theme);
13 | });
14 | };
15 | reader.readAsText(info.file);
16 | };
17 |
18 | return (
19 |
20 |
25 |
26 |
27 |
28 | Drag to upload
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/src/themes/dark-theme.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/color/colorPalette.less';
2 | @import '~antd/dist/antd.less';
3 | @import '~antd/lib/style/themes/default.less';
4 |
5 | @body-background: @black;
6 | @component-background: #141414;
7 | @icon-color-hover: fade(@white, 75%);
8 | @heading-color: fade(@white, 85%);
9 | @text-color: fade(@white, 65%);
10 | @text-color-secondary: fade(@white, 45%);
11 | @border-color-base: #434343;
12 | @border-color-split: #434343;
13 | @border-style-base: solid;
14 | @layout-body-background: @body-background;
15 | @layout-header-background: @component-background;
16 | @layout-sider-background: @component-background;
17 | @layout-trigger-background: @component-background;
18 | @font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Helvetica,
19 | 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif;
20 | @font-feature-settings-base: tnum;
21 | @disabled-color: fade(@white, 30%);
22 | @disabled-color-dark: fade(@white, 30%);
23 | @outline-blur-size: 0;
24 | @background-color-light: fade(@white, 4%);
25 | @background-color-base: fade(@white, 8%);
26 | @item-hover-bg: fade(@white, 8%);
27 | @shadow-color: rgba(0, 0, 0, 0.45);
28 | @shadow-1-up: 0 -6px 16px -8px rgba(0, 0, 0, 0.24), 0 -9px 28px 0 rgba(0, 0, 0, 0.15),
29 | 0 -12px 48px 16px rgba(0, 0, 0, 0.09);
30 | @shadow-1-down: 0 6px 16px -8px rgba(0, 0, 0, 0.24), 0 9px 28px 0 rgba(0, 0, 0, 0.15),
31 | 0 12px 48px 16px rgba(0, 0, 0, 0.09);
32 | @shadow-1-left: -6px 0 16px -8px rgba(0, 0, 0, 0.08), -9px 0 28px 0 rgba(0, 0, 0, 0.05),
33 | -12px 0 48px 16px rgba(0, 0, 0, 0.03);
34 | @shadow-1-right: 6px 0 16px -8px rgba(0, 0, 0, 0.24), 9px 0 28px 0 rgba(0, 0, 0, 0.15),
35 | 12px 0 48px 16px rgba(0, 0, 0, 0.09);
36 | @shadow-2: 0 3px 6px -4px rgba(0, 0, 0, 0.36), 0 6px 16px 0 rgba(0, 0, 0, 0.24),
37 | 0 9px 28px 8px rgba(0, 0, 0, 0.15);
38 | @btn-default-bg: transparent;
39 | @input-placeholder-color: fade(@white, 30%);
40 | @input-bg: transparent;
41 | @input-number-handler-active-bg: @popover-background;
42 | @select-item-selected-font-weight: 600;
43 | @tooltip-bg: #434343;
44 | @popover-bg: @popover-background;
45 | @modal-header-bg: @popover-background;
46 | @menu-popup-bg: @popover-background;
47 | @menu-dark-submenu-bg: @black;
48 | @menu-item-active-bg: #262626;
49 | @table-header-bg: #1d1d1d;
50 | @table-header-sort-bg: #717172;
51 | @table-body-sort-bg: fade(@white, 1%);
52 | @table-row-hover-bg: #262626;
53 | @table-expanded-row-bg: #1d1d1d;
54 | @badge-text-color: @white;
55 | @card-head-background: #1d1d1d;
56 | @card-head-padding: 11px;
57 | @card-actions-background: fade(@white, 4%);
58 | @card-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.48), 0 3px 6px 0 rgba(0, 0, 0, 0.36),
59 | 0 5px 12px 4px rgba(0, 0, 0, 0.27);
60 | @avatar-bg: #5a5a5a;
61 | @pagination-item-bg-active: transparent;
62 | @slider-rail-background-color: fade(@white, 20%);
63 | @slider-rail-background-color-hover: @slider-rail-background-color;
64 | @slider-track-background-color: @primary-color;
65 | @slider-handle-color: @primary-color;
66 | @skeleton-color: #303030;
67 | @alert-success-border-color: @green-3;
68 | @alert-success-bg-color: @green-1;
69 | @alert-info-border-color: @blue-3;
70 | @alert-info-bg-color: @blue-1;
71 | @alert-warning-border-color: @gold-3;
72 | @alert-warning-bg-color: @gold-1;
73 | @alert-error-border-color: @red-3;
74 | @alert-error-bg-color: @red-1;
75 | @alert-success-border-color: @green-5;
76 | @alert-success-bg-color: @green-7;
77 | @alert-info-border-color: @blue-5;
78 | @alert-info-bg-color: @blue-7;
79 | @alert-warning-border-color: @gold-5;
80 | @alert-warning-bg-color: @gold-7;
81 | @alert-error-border-color: @red-5;
82 | @alert-error-bg-color: @red-7;
83 | @layout-footer-background: @layout-header-background;
84 |
--------------------------------------------------------------------------------
/src/views/CustomViewLoader.js:
--------------------------------------------------------------------------------
1 | import React, { Suspense, useEffect, useRef, useState } from 'react';
2 | import LoadingIndicator from '../common/LoadingIndicator';
3 | import { Alert } from 'antd';
4 | import CustomView from '../customView/CustomView';
5 |
6 | export default function CustomViewLoader(props) {
7 | const [Component, setComponent] = useState(null);
8 | const [customView, setCustomView] = useState(null);
9 | const isMounted = useRef(true);
10 |
11 | useEffect(() => {
12 | return () => {
13 | isMounted.current = false;
14 | };
15 | });
16 |
17 | useEffect(() => {
18 | window.api.CVArrayCallback.current.push(getCustomViews);
19 | getCustomViews();
20 | }, [props.match]);
21 |
22 | const getCustomViews = () => {
23 | let _customView = window.api.customViews.current.find(item => {
24 | return item.metadata.name === props.match.params.viewName;
25 | });
26 | if (
27 | _customView &&
28 | _customView.spec.enabled &&
29 | (!customView ||
30 | _customView.metadata.resourceVersion !==
31 | customView.metadata.resourceVersion)
32 | ) {
33 | if (_customView.spec.component) {
34 | _customView.spec.resources.forEach(res => {
35 | if (isMounted.current)
36 | setComponent(
37 | React.lazy(() =>
38 | import(
39 | './' +
40 | res.resourcePath +
41 | (res.resourcePath.slice(-3) === '.js' ? '' : '.js')
42 | )
43 | )
44 | );
45 | });
46 | }
47 | if (isMounted.current) setCustomView(_customView);
48 | }
49 | };
50 |
51 | return customView ? (
52 | customView.spec.component ? (
53 |
54 | }>
55 | {Component ? : null}
56 |
57 |
58 | ) : (
59 |
60 |
61 |
62 | )
63 | ) : (
64 |
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/src/views/PluginLoader.js:
--------------------------------------------------------------------------------
1 | import React, { Suspense, useEffect, useState } from 'react';
2 | import LoadingIndicator from '../common/LoadingIndicator';
3 | import { Alert } from 'antd';
4 |
5 | export default function PluginLoader(props) {
6 | const [Component, setComponent] = useState(null);
7 |
8 | useEffect(() => {
9 | setComponent(React.lazy(() => import('./' + props.resourcePath + '.js')));
10 | }, [props.match]);
11 |
12 | return (
13 |
14 | }>
15 | {Component ? : null}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/views/configView/ConfigView.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqotech/dashboard/ed7982b549b8a6291733d803b3a557d413f4402a/src/views/configView/ConfigView.css
--------------------------------------------------------------------------------
/src/views/liqo/Home.css:
--------------------------------------------------------------------------------
1 | .home-container {
2 | position: relative;
3 | }
4 |
5 | .home-header {
6 | letter-spacing: 0.01em;
7 | box-shadow: 0 2px 2px 0 #ececec, 0 0 0 1px #ececec;
8 | background: white;
9 | min-height: 100%;
10 | }
11 |
--------------------------------------------------------------------------------
/src/widgets/donut/Donut.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Chart, Interval, Tooltip, Legend, Axis, Coordinate } from 'bizcharts';
3 | import { colorsBasic } from '../../services/Colors';
4 |
5 | function Donut(props) {
6 | let empty = {
7 | fc: 'Free',
8 | value: 100
9 | };
10 |
11 | props.data.forEach(d => {
12 | if (!isFinite(d.value)) d.value = 0;
13 | empty.value -= d.value;
14 | });
15 |
16 | empty.value = Math.round(empty.value * 100) / 100;
17 |
18 | props.data.push(empty);
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | {(title, items) => {
27 | return (
28 |
29 |
39 | {items[0].data.fc}:{' '}
40 | {items[0].data.value}%
41 |
42 | );
43 | }}
44 |
45 | {
52 | if (d === 'Free') return '#f3f3f3';
53 | return colorsBasic[
54 | props.data.indexOf(
55 | props.data.find(datum => {
56 | return datum.fc === d;
57 | })
58 | )
59 | ];
60 | }
61 | ]}
62 | animate={false}
63 | />
64 |
65 | );
66 | }
67 |
68 | export default Donut;
69 |
--------------------------------------------------------------------------------
/src/widgets/graph/GraphNet.css:
--------------------------------------------------------------------------------
1 | .graph-network {
2 | background-color: rgb(240, 242, 245);
3 | }
4 |
5 | .graph-network:focus {
6 | border-color: aqua;
7 | }
8 |
--------------------------------------------------------------------------------
/src/widgets/histogram/HistoChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Utils from '../../services/Utils';
3 | import { Chart, Interval } from 'bizcharts';
4 | import { Alert } from 'antd';
5 |
6 | function HistoChart(props) {
7 | const utils = Utils();
8 |
9 | const data = [];
10 | const values = utils.index(props.CR, props.template.spec.values);
11 | const labels = utils.index(props.CR, props.template.spec.labels);
12 | if (values && labels) {
13 | for (let i = 0; i < values.length; i++) {
14 | if (values[i] !== undefined && labels[i] !== undefined) {
15 | data.push({
16 | value: values[i],
17 | label: labels[i]
18 | });
19 | }
20 | }
21 | }
22 |
23 | if (data.length !== 0) {
24 | return (
25 |
26 |
27 |
28 |
29 |
30 | );
31 | } else {
32 | return (
33 |
39 | );
40 | }
41 | }
42 |
43 | export default HistoChart;
44 |
--------------------------------------------------------------------------------
/src/widgets/line/LineChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Chart, LineAdvance, Legend, Axis, Slider } from 'bizcharts';
3 | import { addZero } from '../../views/liqo/HomeUtils';
4 |
5 | function LineChart(props) {
6 | let data = [];
7 | props.data.forEach(res => {
8 | if (!isFinite(res.value)) res.value = 0;
9 | data.push(res);
10 | });
11 |
12 | if (data.length === 0) {
13 | let date = new Date();
14 | let date_format =
15 | addZero(date.getHours()) +
16 | ':' +
17 | addZero(date.getMinutes()) +
18 | ':' +
19 | addZero(date.getSeconds());
20 |
21 | data.push(
22 | { resource: 'CPU', date: date_format, value: 0 },
23 | { resource: 'RAM', date: date_format, value: 0 }
24 | );
25 | }
26 |
27 | return (
28 |
35 |
36 |
37 |
45 | {
56 | return '';
57 | }}
58 | />
59 |
60 | );
61 | }
62 |
63 | export default LineChart;
64 |
--------------------------------------------------------------------------------
/src/widgets/piechart/PieChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Utils from '../../services/Utils';
3 | import { Axis, Chart, Coordinate, Interval, Tooltip } from 'bizcharts';
4 | import { Alert } from 'antd';
5 |
6 | /**
7 | * template is a CR of a CRD template (piechart in this case)
8 | * CR is the spec field of a CR of the user defined CRD that we want to represent
9 | */
10 |
11 | function PieChart(props) {
12 | const utils = Utils();
13 |
14 | const data = [];
15 | const values = utils.index(props.CR, props.template.spec.values);
16 | const labels = utils.index(props.CR, props.template.spec.labels);
17 | if (values && labels) {
18 | for (let i = 0; i < values.length; i++) {
19 | if (values[i] !== undefined && labels[i] !== undefined) {
20 | data.push({
21 | value: values[i],
22 | label: labels[i]
23 | });
24 | }
25 | }
26 | }
27 |
28 | if (data.length !== 0) {
29 | return (
30 |
31 |
32 |
33 |
34 |
35 | {
47 | return data.value;
48 | }
49 | }
50 | ]}
51 | />
52 |
53 |
54 | );
55 | } else {
56 | return (
57 |
63 | );
64 | }
65 | }
66 |
67 | export default PieChart;
68 |
--------------------------------------------------------------------------------
/src/widgets/table/TableViewer.js:
--------------------------------------------------------------------------------
1 | import { Modal, Table, Tooltip } from 'antd';
2 | import React, { useRef, useState } from 'react';
3 | import {
4 | ArrowsAltOutlined,
5 | CheckCircleOutlined,
6 | ExclamationCircleOutlined
7 | } from '@ant-design/icons';
8 | import FormViewer from '../form/FormViewer';
9 | import { Link } from 'react-router-dom';
10 |
11 | export default function TableViewer(props) {
12 | const columns = [];
13 | const dataSource = [];
14 | const form = useRef({ form: {} });
15 | const [showModal, setShowModal] = useState(false);
16 |
17 | if (props.form[props.title].length > 0) {
18 | Object.keys(props.form[props.title][0]).forEach(key => {
19 | columns.push({
20 | title: key,
21 | dataIndex: key,
22 | key: key
23 | });
24 | });
25 |
26 | let counter = 0;
27 |
28 | props.form[props.title].forEach(item => {
29 | let row = {};
30 | row.key = counter;
31 | Object.keys(item).forEach(key => {
32 | if (typeof item[key] !== 'object') {
33 | if (typeof item[key] === 'boolean')
34 | row[key] = item[key] ? (
35 |
36 | ) : (
37 |
38 | );
39 | else row[key] = item[key];
40 | } else {
41 | console.log(key, item[key]);
42 |
43 | row[key] = (
44 |
45 | {
47 | form.current.form = item[key];
48 | setShowModal(true);
49 | }}
50 | >
51 | {key + ' # ' + counter}
52 | setShowModal(true)}
55 | />
56 |
57 |
58 | );
59 | }
60 | });
61 | dataSource.push(row);
62 | counter++;
63 | });
64 | }
65 |
66 | return (
67 | <>
68 | setShowModal(false)}
75 | >
76 |
77 |
83 |
84 |
85 |
99 | >
100 | );
101 | }
102 |
--------------------------------------------------------------------------------
/test/AppFooter.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import React from 'react';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import fetchMock from 'jest-fetch-mock';
5 | import { testTimeout } from '../src/constants';
6 | import { MemoryRouter } from 'react-router-dom';
7 | import App from '../src/app/App';
8 | import { mockCRDAndViewsExtended, setToken } from './RTLUtils';
9 |
10 | fetchMock.enableMocks();
11 |
12 | describe('Footer', () => {
13 | test(
14 | 'Footer is present',
15 | async () => {
16 | mockCRDAndViewsExtended();
17 | setToken();
18 |
19 | localStorage.setItem('theme', 'dark');
20 |
21 | render(
22 |
23 |
24 |
25 | );
26 |
27 | expect(await screen.findByText(/Proudly/i));
28 | },
29 | testTimeout
30 | );
31 | });
32 |
--------------------------------------------------------------------------------
/test/AppHeader.test.js:
--------------------------------------------------------------------------------
1 | import { act, fireEvent, screen, render } from '@testing-library/react';
2 | import React from 'react';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import fetchMock from 'jest-fetch-mock';
5 | import { loginTest, mockCRDAndViewsExtended, setToken } from './RTLUtils';
6 | import userEvent from '@testing-library/user-event';
7 | import { testTimeout } from '../src/constants';
8 | import AppHeader from '../src/common/AppHeader';
9 | import { MemoryRouter } from 'react-router-dom';
10 | import ApiInterface from '../src/services/api/ApiInterface';
11 | import DashboardConfig from '../__mocks__/dashboardconf.json';
12 |
13 | fetchMock.enableMocks();
14 |
15 | beforeEach(() => {
16 | localStorage.setItem('theme', 'dark');
17 | });
18 |
19 | describe('Header', () => {
20 | test(
21 | 'Header main menus item and search bar are showed and working, ' +
22 | 'logout works',
23 | async () => {
24 | mockCRDAndViewsExtended();
25 | await loginTest();
26 |
27 | expect(await screen.findAllByLabelText('question-circle')).toHaveLength(
28 | 3
29 | );
30 | expect(await screen.findByLabelText('logout')).toBeInTheDocument();
31 |
32 | await screen.findByLabelText('autocompletesearch');
33 |
34 | window.api.autoCompleteCallback.current[0]();
35 |
36 | await userEvent.type(screen.getAllByRole('combobox')[0], 'liqodashtest');
37 | let test = await screen.findByText('liqodashtests');
38 | fireEvent.mouseOver(test);
39 | fireEvent.click(test);
40 | expect(await screen.findByText('apis')).toBeInTheDocument();
41 | expect(await screen.findByText('LiqoDashTest')).toBeInTheDocument();
42 | expect(await screen.findByText('test-1')).toBeInTheDocument();
43 |
44 | const logout = await screen.findByLabelText('logout');
45 | userEvent.click(logout);
46 | expect(screen.getByText('Liqo Login'));
47 | },
48 | testTimeout
49 | );
50 |
51 | test(
52 | 'Header info',
53 | async () => {
54 | mockCRDAndViewsExtended();
55 | await loginTest();
56 |
57 | let question = await screen.findAllByLabelText('question-circle');
58 | userEvent.click(question[0]);
59 |
60 | expect(
61 | await screen.findByText('LiqoDash Information')
62 | ).toBeInTheDocument();
63 |
64 | await act(async () => {
65 | let close = await screen.findAllByLabelText('close');
66 | userEvent.click(close[0]);
67 | userEvent.click(close[1]);
68 | await new Promise(r => setTimeout(r, 500));
69 | });
70 | },
71 | testTimeout
72 | );
73 |
74 | test(
75 | 'Toggle dark/light',
76 | async () => {
77 | window.api = ApiInterface({ id_token: 'test' });
78 | setToken();
79 | window.api.dashConfigs.current = DashboardConfig.items[0];
80 | window.less = {
81 | modifyVars: async () => {
82 | return Promise.resolve();
83 | }
84 | };
85 |
86 | render(
87 |
88 |
89 |
90 | );
91 |
92 | const switcher = await screen.findByRole('switch');
93 |
94 | await act(async () => {
95 | userEvent.click(switcher);
96 | await new Promise(r => setTimeout(r, 500));
97 | });
98 |
99 | await act(async () => {
100 | userEvent.click(switcher);
101 | await new Promise(r => setTimeout(r, 500));
102 | });
103 |
104 | userEvent.click(await screen.findByLabelText('crown'));
105 |
106 | userEvent.click(await screen.findByLabelText('folder'));
107 | },
108 | testTimeout
109 | );
110 | });
111 |
--------------------------------------------------------------------------------
/test/DashboardConfig.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom/extend-expect';
3 | import fetchMock from 'jest-fetch-mock';
4 | import { generalMocks, setToken } from './RTLUtils';
5 | import { act, render, screen } from '@testing-library/react';
6 | import { MemoryRouter } from 'react-router-dom';
7 | import App from '../src/app/App';
8 | import { testTimeout } from '../src/constants';
9 | import Cookies from 'js-cookie';
10 | import userEvent from '@testing-library/user-event';
11 | import PodMockResponse from '../__mocks__/pod.json';
12 | import DashboardConfig from '../__mocks__/dashboardconf.json';
13 | import CRDmockResponse from '../__mocks__/crd_fetch_long.json';
14 |
15 | fetchMock.enableMocks();
16 |
17 | async function setup() {
18 | setToken();
19 | window.history.pushState(
20 | {},
21 | 'Page Title',
22 | '/api/v1/namespaces/test/pods/hello-world-deployment-6756549f5-x66v9'
23 | );
24 |
25 | return render(
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | beforeEach(() => {
33 | localStorage.setItem('theme', 'dark');
34 | Cookies.remove('token');
35 | });
36 |
37 | function mocks() {
38 | fetch.mockResponse(req => {
39 | if (
40 | req.url === 'http://localhost:3001/clustercustomobject/dashboardconfigs'
41 | ) {
42 | if (req.method === 'GET')
43 | return Promise.resolve(
44 | new Response(JSON.stringify({ body: { items: [] } }))
45 | );
46 | else if (req.method === 'POST') {
47 | return Promise.resolve(
48 | new Response(JSON.stringify({ body: DashboardConfig.items[0] }))
49 | );
50 | }
51 | } else if (generalMocks(req.url)) return generalMocks(req.url);
52 | });
53 | }
54 |
55 | describe('DashboardConfig', () => {
56 | test('Dashboard config create when there is none', async () => {
57 | mocks();
58 |
59 | await setup();
60 |
61 | expect(
62 | await screen.findByText('hello-world-deployment-6756549f5-x66v9')
63 | ).toBeInTheDocument();
64 |
65 | expect(window.api.dashConfigs.current).not.toBeNull();
66 | });
67 |
68 | test('Dashboard config update works', async () => {
69 | mocks();
70 |
71 | await setup();
72 |
73 | expect(
74 | await screen.findByText('hello-world-deployment-6756549f5-x66v9')
75 | ).toBeInTheDocument();
76 |
77 | let apiManager = window.api.apiManager.current;
78 |
79 | let dashConf = DashboardConfig.items[0];
80 |
81 | dashConf.metadata.resourceVersion += 1;
82 |
83 | await act(async () => {
84 | apiManager.sendModifiedSignal('dashboardconfigs/', dashConf);
85 | await new Promise(r => setTimeout(r, 1000));
86 | });
87 | });
88 |
89 | test('Dashboard config delete generate a new config', async () => {
90 | mocks();
91 |
92 | await setup();
93 |
94 | expect(
95 | await screen.findByText('hello-world-deployment-6756549f5-x66v9')
96 | ).toBeInTheDocument();
97 |
98 | let apiManager = window.api.apiManager.current;
99 |
100 | let dashConf = DashboardConfig;
101 |
102 | dashConf.metadata.resourceVersion += 1;
103 |
104 | await act(async () => {
105 | apiManager.sendDeletedSignal('dashboardconfigs/', dashConf);
106 | await new Promise(r => setTimeout(r, 1000));
107 | });
108 |
109 | expect(window.api.dashConfigs.current).not.toBeNull();
110 | });
111 | });
112 |
--------------------------------------------------------------------------------
/test/Donut.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom/extend-expect';
3 | import fetchMock from 'jest-fetch-mock';
4 | import { fireEvent, render } from '@testing-library/react';
5 | import { MemoryRouter } from 'react-router-dom';
6 | import Donut from '../src/widgets/donut/Donut';
7 | import { testTimeout } from '../src/constants';
8 |
9 | fetchMock.enableMocks();
10 |
11 | describe('Donut', () => {
12 | test(
13 | 'Line chart NaN data',
14 | async () => {
15 | render(
16 |
17 |
18 |
19 | );
20 | },
21 | testTimeout
22 | );
23 | });
24 |
--------------------------------------------------------------------------------
/test/ErrorRedirect.test.js:
--------------------------------------------------------------------------------
1 | import { screen, render } from '@testing-library/react';
2 | import React from 'react';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import userEvent from '@testing-library/user-event';
5 | import fetchMock from 'jest-fetch-mock';
6 | import CRDMockResponse from '../__mocks__/crd_fetch.json';
7 | import Error401 from '../__mocks__/401.json';
8 | import Error403 from '../__mocks__/403.json';
9 | import Error500 from '../__mocks__/500.json';
10 | import { alwaysPresentGET, generalHomeGET, loginTest } from './RTLUtils';
11 | import ViewMockResponse from '../__mocks__/views.json';
12 | import { testTimeout } from '../src/constants';
13 | import Cookies from 'js-cookie';
14 | import ErrorRedirect from '../src/error-handles/ErrorRedirect';
15 |
16 | fetchMock.enableMocks();
17 |
18 | async function setup(error) {
19 | fetch.mockImplementation(async url => {
20 | if (url === 'http://localhost:3001/customresourcedefinition') {
21 | return Promise.resolve(new Response(JSON.stringify(CRDMockResponse)));
22 | } else if (url === 'http://localhost:3001/clustercustomobject/views') {
23 | return Promise.resolve(
24 | new Response(JSON.stringify({ body: ViewMockResponse }))
25 | );
26 | } else if (
27 | url ===
28 | 'http://localhost:/apiserver/apis/apiextensions.k8s.io/v1/customresourcedefinitions/liqodashtests.dashboard.liqo.io'
29 | ) {
30 | if (error === 401) {
31 | return Promise.reject(Error401.body);
32 | } else if (error === 403) {
33 | return Promise.reject(Error403.body);
34 | } else if (error === 500) {
35 | return Promise.reject();
36 | }
37 | } else if (alwaysPresentGET(url)) {
38 | return alwaysPresentGET(url);
39 | } else {
40 | return generalHomeGET(url);
41 | }
42 | });
43 |
44 | await loginTest();
45 |
46 | const customview = screen.getByText('Custom Resources');
47 | userEvent.click(customview);
48 |
49 | userEvent.click(await screen.findByText('liqodashtests.dashboard.liqo.io'));
50 | }
51 |
52 | beforeEach(() => {
53 | localStorage.setItem('theme', 'dark');
54 | Cookies.remove('token');
55 | });
56 |
57 | describe('ErrorRedirect', () => {
58 | test(
59 | '401 redirect works',
60 | async () => {
61 | //await setup(401);
62 | render(
63 |
67 | );
68 | expect(await screen.findByText(/401/i)).toBeInTheDocument();
69 | },
70 | testTimeout
71 | );
72 |
73 | test(
74 | '403 redirect works',
75 | async () => {
76 | //await setup(403);
77 | render(
78 |
82 | );
83 | expect(await screen.findByText(/403/i)).toBeInTheDocument();
84 | },
85 | testTimeout
86 | );
87 |
88 | test(
89 | 'Default redirect works',
90 | async () => {
91 | //await setup(500);
92 | render(
93 | {}}
96 | />
97 | );
98 | expect(await screen.findByText(/error/i)).toBeInTheDocument();
99 |
100 | userEvent.click(screen.getByText(/logout/i));
101 | },
102 | testTimeout
103 | );
104 | });
105 |
--------------------------------------------------------------------------------
/test/HistoChart.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import fetchMock from 'jest-fetch-mock';
5 | import ApiInterface from '../src/services/api/ApiInterface';
6 | import CRDmockEmpty from '../__mocks__/crd_fetch.json';
7 | import ViewMockResponse from '../__mocks__/views.json';
8 | import LiqoDashMockResponse from '../__mocks__/liqodashtest.json';
9 | import HistoMockResponse from '../__mocks__/histocharts.json';
10 | import HistoMockResponseWrong from '../__mocks__/histocharts_wrong.json';
11 | import HistoChart from '../src/widgets/histogram/HistoChart';
12 | import { testTimeout } from '../src/constants';
13 |
14 | fetchMock.enableMocks();
15 |
16 | async function setup(histo) {
17 | fetch.mockImplementation(url => {
18 | if (url === 'http://localhost:3001/customresourcedefinition') {
19 | return Promise.resolve(new Response(JSON.stringify(CRDmockEmpty)));
20 | } else if (url === 'http://localhost:3001/clustercustomobject/views') {
21 | return Promise.resolve(
22 | new Response(JSON.stringify({ body: ViewMockResponse }))
23 | );
24 | } else if (
25 | url === 'http://localhost:3001/clustercustomobject/liqodashtests'
26 | ) {
27 | return Promise.resolve(
28 | new Response(JSON.stringify({ body: LiqoDashMockResponse }))
29 | );
30 | } else if (
31 | url === 'http://localhost:3001/clustercustomobject/histocharts'
32 | ) {
33 | return Promise.resolve(new Response(JSON.stringify({ body: histo })));
34 | }
35 | });
36 |
37 | window.api = ApiInterface({ id_token: 'test' });
38 | await window.api.getCRDs().then(async () => {
39 | let liqo_crd = await api.getCRDFromKind('LiqoDashTest');
40 | let histo_crd = await api.getCRDFromKind('HistoChart');
41 | let liqo_cr = await api.getCustomResourcesAllNamespaces(liqo_crd);
42 | let histo_cr = await api.getCustomResourcesAllNamespaces(histo_crd);
43 |
44 | render(
45 |
49 | );
50 | });
51 | }
52 |
53 | describe('HistoChart', () => {
54 | test(
55 | 'Histogram chart is well formed',
56 | async () => {
57 | await setup(HistoMockResponse);
58 | },
59 | testTimeout
60 | );
61 |
62 | test(
63 | 'Histogram chart shows error when wrong input',
64 | async () => {
65 | await setup(HistoMockResponseWrong);
66 | expect(await screen.findByText(/Something/i)).toBeInTheDocument();
67 | },
68 | testTimeout
69 | );
70 | });
71 |
--------------------------------------------------------------------------------
/test/LineChart.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom/extend-expect';
3 | import fetchMock from 'jest-fetch-mock';
4 | import { render } from '@testing-library/react';
5 | import { MemoryRouter } from 'react-router-dom';
6 | import LineChart from '../src/widgets/line/LineChart';
7 | import { testTimeout } from '../src/constants';
8 |
9 | fetchMock.enableMocks();
10 |
11 | describe('Status', () => {
12 | test(
13 | 'Line chart NaN data',
14 | async () => {
15 | render(
16 |
17 |
23 |
24 | );
25 | },
26 | testTimeout
27 | );
28 | });
29 |
--------------------------------------------------------------------------------
/test/LiqoHeader.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom/extend-expect';
3 | import fetchMock from 'jest-fetch-mock';
4 | import { render, screen } from '@testing-library/react';
5 | import ApiInterface from '../src/services/api/ApiInterface';
6 | import { MemoryRouter } from 'react-router-dom';
7 | import { testTimeout } from '../src/constants';
8 | import CMMockResponse from '../__mocks__/configmap_clusterID.json';
9 | import ConfigMockResponse from '../__mocks__/configs.json';
10 | import Error500 from '../__mocks__/500.json';
11 | import LiqoHeader from '../src/views/liqo/LiqoHeader';
12 | import userEvent from '@testing-library/user-event';
13 |
14 | fetchMock.enableMocks();
15 |
16 | async function setup() {
17 | let props = { history: [] };
18 | window.api = ApiInterface({ id_token: 'test' }, props);
19 | return render(
20 |
21 |
22 |
23 | );
24 | }
25 |
26 | function mocks(error, errorCM) {
27 | fetch.mockResponse(req => {
28 | if (
29 | req.url === 'http://localhost:3001/clustercustomobject/clusterconfigs'
30 | ) {
31 | if (req.method === 'PUT') {
32 | if (!error) {
33 | let config = ConfigMockResponse;
34 | config.items[0].spec.discoveryConfig.clusterName = 'My cluster';
35 | return Promise.resolve(
36 | new Response(JSON.stringify({ body: config }))
37 | );
38 | } else return Promise.reject();
39 | } else
40 | return Promise.resolve(
41 | new Response(JSON.stringify({ body: ConfigMockResponse }))
42 | );
43 | } else if (req.url === 'http://localhost:3001/configmaps/liqo') {
44 | if (errorCM) return Promise.reject(Error500.body);
45 | else
46 | return Promise.resolve(
47 | new Response(JSON.stringify({ body: CMMockResponse }))
48 | );
49 | }
50 | });
51 | }
52 |
53 | async function changeClusterName() {
54 | await setup();
55 |
56 | const clusterName = await screen.findByText('LIQO');
57 | const clusterID = await screen.findByText(
58 | '10e3e821-8194-4fb1-856f-d917d2fc54c0'
59 | );
60 |
61 | expect(clusterName).toBeInTheDocument();
62 | expect(clusterID).toBeInTheDocument();
63 |
64 | userEvent.click(clusterName);
65 | await userEvent.type(
66 | await screen.findByRole('textbox'),
67 | '{backspace}{backspace}{backspace}{backspace}My cluster'
68 | );
69 | userEvent.click(clusterID);
70 | }
71 |
72 | describe('LiqoHeader', () => {
73 | test(
74 | 'Error on saving clusterName',
75 | async () => {
76 | mocks(true);
77 |
78 | await changeClusterName();
79 |
80 | expect(await screen.findByText('LIQO')).toBeInTheDocument();
81 | },
82 | testTimeout
83 | );
84 |
85 | test(
86 | 'The header shows the cluster name and cluster ID',
87 | async () => {
88 | mocks();
89 |
90 | await changeClusterName();
91 |
92 | expect(await screen.findByText('My cluster')).toBeInTheDocument();
93 | expect(await screen.queryByText('LIQO')).not.toBeInTheDocument();
94 | },
95 | testTimeout
96 | );
97 |
98 | test(
99 | 'Error on getting configmap',
100 | async () => {
101 | mocks(false, true);
102 | await setup();
103 | },
104 | testTimeout
105 | );
106 | });
107 |
--------------------------------------------------------------------------------
/test/ListHeader.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom/extend-expect';
3 | import fetchMock from 'jest-fetch-mock';
4 | import { generalMocks, setToken } from './RTLUtils';
5 | import { act, fireEvent, render, screen } from '@testing-library/react';
6 | import { MemoryRouter } from 'react-router-dom';
7 | import App from '../src/app/App';
8 | import { testTimeout } from '../src/constants';
9 | import Cookies from 'js-cookie';
10 | import userEvent from '@testing-library/user-event';
11 | import PodMockResponse from '../__mocks__/pod.json';
12 |
13 | fetchMock.enableMocks();
14 |
15 | async function setup() {
16 | setToken();
17 | window.history.pushState({}, 'Page Title', '/api/v1/pods');
18 |
19 | return render(
20 |
21 |
22 |
23 | );
24 | }
25 |
26 | beforeEach(() => {
27 | localStorage.setItem('theme', 'dark');
28 | Cookies.remove('token');
29 | });
30 |
31 | function mocks() {
32 | fetch.mockImplementation(url => {
33 | if (generalMocks(url)) return generalMocks(url);
34 | });
35 | }
36 |
37 | describe('ListHeader', () => {
38 | test(
39 | 'Header list add pod',
40 | async () => {
41 | mocks();
42 |
43 | await setup();
44 |
45 | expect(await screen.findByText('Pod')).toBeInTheDocument();
46 |
47 | userEvent.click(screen.getByLabelText('plus'));
48 |
49 | expect(
50 | await screen.findByText(/create a new pod resource/i)
51 | ).toBeInTheDocument();
52 |
53 | let res = PodMockResponse;
54 | res.metadata.name += '2';
55 |
56 | await userEvent.type(
57 | screen.getByLabelText('editor'),
58 | JSON.stringify(res)
59 | );
60 |
61 | userEvent.click(screen.getByRole('button', { name: 'Save' }));
62 |
63 | expect(await screen.findAllByRole('row')).toHaveLength(4);
64 | },
65 | testTimeout
66 | );
67 |
68 | test(
69 | 'Change namespace change resources',
70 | async () => {
71 | mocks();
72 |
73 | await setup();
74 |
75 | expect(await screen.findByText('Pod')).toBeInTheDocument();
76 | expect(await screen.findAllByRole('row')).toHaveLength(4);
77 |
78 | const ns = screen.getByText('all namespaces');
79 | userEvent.click(ns);
80 |
81 | const ns_liqo = await screen.findByText('liqo');
82 | const ns_default = await screen.findAllByText('default');
83 |
84 | expect(ns_liqo).toBeInTheDocument();
85 | expect(ns_default[1]).toBeInTheDocument();
86 |
87 | await act(async () => {
88 | fireEvent.mouseOver(ns_liqo);
89 | fireEvent.click(ns_liqo);
90 |
91 | await new Promise(r => setTimeout(r, 1000));
92 | });
93 |
94 | expect(await screen.findByText(/no data/i)).toBeInTheDocument();
95 | },
96 | testTimeout
97 | );
98 | });
99 |
--------------------------------------------------------------------------------
/test/Login.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import React from 'react';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import fetchMock from 'jest-fetch-mock';
5 | import { setup_login, token } from './RTLUtils';
6 | import { testTimeout } from '../src/constants';
7 | import Error401 from '../__mocks__/401.json';
8 | import userEvent from '@testing-library/user-event';
9 | import NodesMockResponse from '../__mocks__/nodes.json';
10 | import Cookies from 'js-cookie';
11 | import { MemoryRouter } from 'react-router-dom';
12 | import App from '../src/app/App';
13 |
14 | fetchMock.enableMocks();
15 |
16 | beforeEach(() => {
17 | localStorage.setItem('theme', 'dark');
18 | Cookies.remove('token');
19 | });
20 |
21 | describe('Login', () => {
22 | test(
23 | 'Login when no CRDs return error',
24 | async () => {
25 | fetch.mockImplementation(url => {
26 | if (url === 'http://localhost:3001/customresourcedefinition') {
27 | return Promise.reject(Error401.body);
28 | } else if (url === 'http://localhost:3001/nodes') {
29 | return Promise.resolve(
30 | new Response(JSON.stringify({ body: NodesMockResponse }))
31 | );
32 | }
33 | });
34 |
35 | setup_login();
36 |
37 | /** Input mock password */
38 | const tokenInput = screen.getByLabelText('lab');
39 | await userEvent.type(tokenInput, token);
40 |
41 | /** Click on login button */
42 | const submitButton = screen.getByRole('button');
43 | userEvent.click(submitButton);
44 | },
45 | testTimeout
46 | );
47 |
48 | test(
49 | 'Login wrong token error',
50 | async () => {
51 | fetch.mockImplementation(url => {
52 | if (url === 'http://localhost:3001/nodes') {
53 | return Promise.reject(Error401.body);
54 | }
55 | });
56 |
57 | render(
58 |
59 |
60 |
61 | );
62 |
63 | /** Input mock password */
64 | const tokenInput = screen.getByLabelText('lab');
65 | await userEvent.type(tokenInput, 'password');
66 |
67 | /** Click on login button */
68 | const submitButton = screen.getByRole('button');
69 | userEvent.click(submitButton);
70 |
71 | expect(await screen.findByText(/not valid/i)).toBeInTheDocument();
72 | },
73 | testTimeout
74 | );
75 | });
76 |
--------------------------------------------------------------------------------
/test/OAPIV3.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import React from 'react';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import fetchMock from 'jest-fetch-mock';
5 | import { testTimeout } from '../src/constants';
6 | import CustomFieldTemplate, {
7 | checkChildren
8 | } from '../src/editors/OAPIV3FormGenerator/CustomFieldTemplate';
9 | import { MemoryRouter } from 'react-router-dom';
10 | import userEvent from '@testing-library/user-event';
11 |
12 | fetchMock.enableMocks();
13 |
14 | describe('OAPIV3', () => {
15 | test(
16 | 'No properties in schema',
17 | async () => {
18 | checkChildren({ schema: {} });
19 | },
20 | testTimeout
21 | );
22 |
23 | test(
24 | 'Footer is present',
25 | async () => {
26 | render(
27 |
28 | {}}
36 | />
37 |
38 | );
39 |
40 | await userEvent.type(await screen.findByDisplayValue('label'), 'l');
41 | },
42 | testTimeout
43 | );
44 | });
45 |
--------------------------------------------------------------------------------
/test/PieChart.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import fetchMock from 'jest-fetch-mock';
5 | import ApiInterface from '../src/services/api/ApiInterface';
6 | import CRDmockEmpty from '../__mocks__/crd_fetch.json';
7 | import ViewMockResponse from '../__mocks__/views.json';
8 | import LiqoDashMockResponse from '../__mocks__/liqodashtest.json';
9 | import PieMockResponse from '../__mocks__/piecharts.json';
10 | import PieMockResponseWrong from '../__mocks__/piecharts_wrong.json';
11 | import PieChart from '../src/widgets/piechart/PieChart';
12 | import { testTimeout } from '../src/constants';
13 |
14 | fetchMock.enableMocks();
15 |
16 | async function setup(pie) {
17 | fetch.mockImplementation(url => {
18 | if (url === 'http://localhost:3001/customresourcedefinition') {
19 | return Promise.resolve(new Response(JSON.stringify(CRDmockEmpty)));
20 | } else if (url === 'http://localhost:3001/clustercustomobject/views') {
21 | return Promise.resolve(
22 | new Response(JSON.stringify({ body: ViewMockResponse }))
23 | );
24 | } else if (
25 | url === 'http://localhost:3001/clustercustomobject/liqodashtests'
26 | ) {
27 | return Promise.resolve(
28 | new Response(JSON.stringify({ body: LiqoDashMockResponse }))
29 | );
30 | } else if (url === 'http://localhost:3001/clustercustomobject/piecharts') {
31 | return Promise.resolve(new Response(JSON.stringify({ body: pie })));
32 | }
33 | });
34 |
35 | window.api = ApiInterface({ id_token: 'test' });
36 | window.api.getCRDs().then(async () => {
37 | let liqo_crd = await window.api.getCRDFromKind('LiqoDashTest');
38 | let pie_crd = await window.api.getCRDFromKind('PieChart');
39 | let liqo_cr = await window.api.getCustomResourcesAllNamespaces(liqo_crd);
40 | let pie_cr = await window.api.getCustomResourcesAllNamespaces(pie_crd);
41 |
42 | render(
43 |
47 | );
48 | });
49 | }
50 |
51 | describe('PieChart', () => {
52 | test(
53 | 'Pie chart is well formed',
54 | async () => {
55 | await setup(PieMockResponse);
56 | },
57 | testTimeout
58 | );
59 |
60 | test(
61 | 'Pie chart show error when wrong input',
62 | async () => {
63 | await setup(PieMockResponseWrong);
64 | expect(await screen.findByText(/Something/i)).toBeInTheDocument();
65 | },
66 | testTimeout
67 | );
68 | });
69 |
--------------------------------------------------------------------------------
/test/ResourceForm.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom/extend-expect';
3 | import fetchMock from 'jest-fetch-mock';
4 | import { generalMocks, setToken } from './RTLUtils';
5 | import { fireEvent, render, screen } from '@testing-library/react';
6 | import { MemoryRouter } from 'react-router-dom';
7 | import App from '../src/app/App';
8 | import { testTimeout } from '../src/constants';
9 | import Cookies from 'js-cookie';
10 | import userEvent from '@testing-library/user-event';
11 | import PodMockResponse from '../__mocks__/pod.json';
12 |
13 | fetchMock.enableMocks();
14 |
15 | async function setup() {
16 | setToken();
17 | window.history.pushState(
18 | {},
19 | 'Page Title',
20 | '/api/v1/namespaces/test/pods/hello-world-deployment-6756549f5-x66v9'
21 | );
22 |
23 | return render(
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | beforeEach(() => {
31 | localStorage.setItem('theme', 'dark');
32 | Cookies.remove('token');
33 | });
34 |
35 | function mocks() {
36 | fetch.mockResponse(req => {
37 | if (generalMocks(req.url)) return generalMocks(req.url);
38 | });
39 | }
40 |
41 | describe('ResourceForm', () => {
42 | test(
43 | 'Search property works',
44 | async () => {
45 | mocks();
46 |
47 | await setup();
48 |
49 | expect(await screen.findByText('pods')).toBeInTheDocument();
50 | expect(
51 | await screen.findByText('hello-world-deployment-6756549f5-x66v9')
52 | ).toBeInTheDocument();
53 |
54 | let select = await screen.findAllByLabelText('select-k8s');
55 | userEvent.click(select[1]);
56 |
57 | await userEvent.type(select[1], 'labels');
58 |
59 | let labels = await screen.findAllByText('labels');
60 |
61 | fireEvent.mouseOver(labels[0]);
62 | fireEvent.click(labels[0]);
63 |
64 | expect(await screen.findByText('Metadata > Labels')).toBeInTheDocument();
65 | userEvent.click(await screen.findByText('Metadata > Labels'));
66 |
67 | const edit = await screen.findAllByLabelText('edit');
68 | userEvent.click(edit[0]);
69 |
70 | let textboxes = await screen.findAllByRole('textbox');
71 |
72 | await userEvent.type(textboxes[1], '2');
73 |
74 | userEvent.click(await screen.findByText('Save changes'));
75 | },
76 | testTimeout
77 | );
78 | });
79 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const path = require('path');
3 | const fs = require('fs');
4 | const webpack = require('webpack');
5 | const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin');
6 | const AntDesignThemePlugin = require('antd-theme-webpack-plugin');
7 | const { getLessVars } = require('antd-theme-generator');
8 | const themeVariables = getLessVars(
9 | path.join(__dirname, './src/styles/variables.less')
10 | );
11 | const lightVars = { ...getLessVars('./src/themes/light-theme.less') };
12 | fs.writeFileSync('./src/themes/dark.json', JSON.stringify(themeVariables));
13 | fs.writeFileSync('./src/themes/light.json', JSON.stringify(lightVars));
14 |
15 | const options = {
16 | antDir: path.join(__dirname, './node_modules/antd'),
17 | stylesDir: path.join(__dirname, './src/styles'),
18 | varFile: path.join(__dirname, './src/styles/variables.less'),
19 | themeVariables: Array.from(new Set([...Object.keys(themeVariables)])),
20 | generateOnce: false,
21 | indexFileName: 'index.html'
22 | };
23 |
24 | const themePlugin = new AntDesignThemePlugin(options);
25 |
26 | module.exports = {
27 | context: __dirname,
28 | entry: ['@babel/polyfill', './src/index.js'],
29 | output: {
30 | path: path.resolve(__dirname, 'dist'),
31 | filename: 'bundle.js',
32 | publicPath: '/'
33 | },
34 | devServer: {
35 | host: '0.0.0.0',
36 | port: 8000,
37 | historyApiFallback: {
38 | disableDotRule: true
39 | }
40 | },
41 | module: {
42 | rules: [
43 | {
44 | test: /\.(js|jsx)$/,
45 | use: 'babel-loader'
46 | },
47 | {
48 | test: /\.css$/,
49 | use: ['style-loader', 'css-loader']
50 | },
51 | {
52 | test: /\.(gif|png|jpe?g|svg)$/i,
53 | use: [
54 | {
55 | loader: 'file-loader',
56 | options: {
57 | esModule: false
58 | }
59 | }
60 | ]
61 | },
62 | {
63 | test: /\.less$/,
64 | use: [
65 | { loader: 'style-loader' },
66 | { loader: 'css-loader' },
67 | {
68 | loader: 'less-loader',
69 | options: {
70 | lessOptions: {
71 | javascriptEnabled: true
72 | }
73 | }
74 | }
75 | ]
76 | },
77 | {
78 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
79 | loader: 'url-loader?limit=10000&mimetype=application/font-woff'
80 | },
81 | {
82 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
83 | loader: 'file-loader'
84 | }
85 | ]
86 | },
87 | plugins: [
88 | new HtmlWebpackPlugin({
89 | filename: 'index.html',
90 | title: process.env.LIQO_DASH_PAGE_TITLE || 'LiqoDash',
91 | meta: {
92 | viewport: 'width=device-width, initial-scale=1',
93 | 'theme-color': '#000000',
94 | description: 'Liqo dashboard'
95 | },
96 | favicon: process.env.LIQO_DASH_FAVICON_PATH || 'src/assets/logo_4.png'
97 | }),
98 | themePlugin,
99 | new AntdDayjsWebpackPlugin(),
100 | new webpack.DefinePlugin({
101 | 'process.env.PUBLIC_PATH': JSON.stringify(''),
102 | APISERVER_URL: JSON.stringify(process.env.APISERVER_URL),
103 | OIDC_PROVIDER_URL: JSON.stringify(process.env.OIDC_PROVIDER_URL),
104 | OIDC_CLIENT_ID: JSON.stringify(process.env.OIDC_CLIENT_ID),
105 | OIDC_REDIRECT_URI: JSON.stringify(process.env.OIDC_REDIRECT_URI),
106 | OIDC_CLIENT_SECRET: JSON.stringify(process.env.OIDC_CLIENT_SECRET)
107 | })
108 | ]
109 | };
110 |
--------------------------------------------------------------------------------