├── .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 | } 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 |
21 | 33 |
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 |
42 |
43 |
44 |
45 |
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 |
86 | 98 | 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 | --------------------------------------------------------------------------------