├── .dockerignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── assignproject.yml │ ├── build.yml │ ├── closepr.yml │ ├── labelpr.yml │ ├── prbuild.yml │ ├── raise-operator-pr.yml │ ├── release.yml │ └── tag.yml ├── .gitignore ├── .npmrc ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── code-of-conduct.md ├── contributing.md ├── docker-compose.yml ├── helm-charts └── move2kube │ ├── Chart.yaml │ ├── LICENSE │ ├── README.md │ ├── templates │ ├── NOTES.txt │ ├── auth-server-deployment.yaml │ ├── auth-server-secret.yaml │ ├── auth-server-service.yaml │ ├── database-deployment.yaml │ ├── database-service.yaml │ ├── move2kube-api-headless-service.yaml │ ├── move2kube-api-ingress.yaml │ ├── move2kube-api-route.yaml │ ├── move2kube-api-secret.yaml │ ├── move2kube-api-service.yaml │ ├── move2kube-api-stateful-set.yaml │ ├── persistent-volume-claim.yaml │ ├── security-context-constraints.yaml │ └── service-account.yaml │ ├── values.schema.json │ └── values.yaml ├── operator ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── bundle.Dockerfile ├── config │ ├── crd │ │ ├── bases │ │ │ ├── konveyor.io_move2kubes.yaml │ │ │ └── move2kube.konveyor.io_move2kubes.yaml │ │ └── kustomization.yaml │ ├── default │ │ ├── kustomization.yaml │ │ ├── manager_auth_proxy_patch.yaml │ │ └── manager_config_patch.yaml │ ├── manager │ │ ├── kustomization.yaml │ │ └── manager.yaml │ ├── manifests │ │ ├── bases │ │ │ └── move2kube-operator.clusterserviceversion.yaml │ │ └── kustomization.yaml │ ├── prometheus │ │ ├── kustomization.yaml │ │ └── monitor.yaml │ ├── rbac │ │ ├── auth_proxy_client_clusterrole.yaml │ │ ├── auth_proxy_role.yaml │ │ ├── auth_proxy_role_binding.yaml │ │ ├── auth_proxy_service.yaml │ │ ├── kustomization.yaml │ │ ├── leader_election_role.yaml │ │ ├── leader_election_role_binding.yaml │ │ ├── move2kube_editor_role.yaml │ │ ├── move2kube_viewer_role.yaml │ │ ├── role.yaml │ │ ├── role_binding.yaml │ │ └── service_account.yaml │ ├── samples │ │ ├── kustomization.yaml │ │ └── move2kube_v1alpha1_move2kube.yaml │ └── scorecard │ │ ├── bases │ │ └── config.yaml │ │ ├── kustomization.yaml │ │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── watches.yaml ├── package.json ├── pnpm-lock.yaml ├── podman-compose.yml ├── public ├── favicon.ico └── index.html ├── releasenotes-config.js ├── src ├── App.tsx ├── app │ ├── hooks.ts │ └── store.ts ├── assets │ └── images │ │ └── logo.svg ├── features │ ├── breadcrumbs │ │ └── BreadCrumbs.tsx │ ├── common │ │ ├── constants.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── inputs │ │ ├── Inputs.tsx │ │ ├── inputsApi.ts │ │ └── inputsSlice.ts │ ├── login │ │ ├── Login.tsx │ │ └── loginApi.ts │ ├── notfound │ │ └── NotFound.tsx │ ├── outputs │ │ ├── Outputs.tsx │ │ ├── graph │ │ │ └── Graph.tsx │ │ ├── outputsApi.ts │ │ ├── outputsSlice.ts │ │ └── qa │ │ │ ├── Confirm.tsx │ │ │ ├── Hints.tsx │ │ │ ├── Input.tsx │ │ │ ├── MultiLineInput.tsx │ │ │ ├── MultiSelect.tsx │ │ │ ├── Password.tsx │ │ │ ├── QAWizard.tsx │ │ │ ├── Select.tsx │ │ │ └── types.ts │ ├── plan │ │ ├── Plan.tsx │ │ ├── planApi.ts │ │ └── planSlice.ts │ ├── projects │ │ ├── Project.tsx │ │ ├── Projects.tsx │ │ └── projectsApi.ts │ ├── support │ │ ├── Support.tsx │ │ └── supportApi.ts │ ├── toasts │ │ ├── Toasts.tsx │ │ └── toastsSlice.ts │ └── workspaces │ │ ├── Workspaces.tsx │ │ └── workspacesApi.ts ├── index.css ├── index.tsx ├── react-app-env.d.ts └── setupProxy.js └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corporation 2023 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | .dockerignore 16 | /.git/ 17 | contributing.md 18 | code-of-conduct.md 19 | Dockerfile 20 | Makefile 21 | README.md 22 | /data/ 23 | /build/ 24 | /helm-charts/ 25 | /node_modules/ 26 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | }, 8 | settings: { react: { version: 'detect' } }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:react/recommended', 12 | 'plugin:react/jsx-runtime', 13 | 'plugin:@typescript-eslint/recommended', 14 | "plugin:react-hooks/recommended", 15 | ], 16 | parser: '@typescript-eslint/parser', 17 | parserOptions: { 18 | ecmaFeatures: { 19 | jsx: true, 20 | }, 21 | ecmaVersion: 12, 22 | sourceType: 'module', 23 | }, 24 | plugins: ['react', '@typescript-eslint'], 25 | rules: { 26 | "@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }] 27 | }, 28 | }; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/workflows/assignproject.yml: -------------------------------------------------------------------------------- 1 | name: Assign Project 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | create_card: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: create project card 12 | uses: peter-evans/create-or-update-project-card@v1 13 | with: 14 | token: ${{ secrets.MOVE2KUBE_PATOKEN }} 15 | project-name: Move2Kube 16 | column-name: Backlog 17 | project-location: konveyor -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - "main" 8 | - "release-*" 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-node@v2-beta 21 | with: 22 | node-version: "16.16.0" 23 | - run: corepack enable 24 | - run: make install 25 | - run: make build 26 | - if: failure() 27 | uses: rtCamp/action-slack-notify@v2 28 | env: 29 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 30 | SLACK_COLOR: "#BD3232" 31 | SLACK_ICON: https://github.com/actions.png?size=48 32 | SLACK_MESSAGE: "Build failed in move2kube-ui ${{ github.ref }}" 33 | SLACK_TITLE: Failed 34 | SLACK_USERNAME: GitHubActions 35 | 36 | image_build: 37 | needs: [build] 38 | name: Image build 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - id: image_tag 43 | run: | 44 | BRANCH="${GITHUB_REF#refs/heads/}" 45 | if [ "$BRANCH" == 'main' ] ; then 46 | echo "tag=latest" >> $GITHUB_OUTPUT 47 | else 48 | echo "tag=$BRANCH" >> $GITHUB_OUTPUT 49 | fi 50 | - name: pull latest image to reuse layers 51 | run: | 52 | docker pull quay.io/konveyor/move2kube-ui:latest || true 53 | docker pull quay.io/konveyor/move2kube-ui-builder:latest || true 54 | - run: echo "${{ secrets.QUAY_BOT_PASSWORD }}" | docker login --username "${{ secrets.QUAY_BOT_USERNAME }}" --password-stdin quay.io 55 | - name: Set up QEMU 56 | uses: docker/setup-qemu-action@v2 57 | - name: Set up Docker Buildx 58 | id: buildx 59 | uses: docker/setup-buildx-action@v2 60 | - name: build image 61 | run: VERSION='${{ steps.image_tag.outputs.tag }}' make cmultibuildpush 62 | - name: build and push the operator container image 63 | run: | 64 | cd operator/ || exit 1 65 | VERSION='${{ steps.image_tag.outputs.tag }}' make docker-build 66 | VERSION='${{ steps.image_tag.outputs.tag }}' make docker-push 67 | - name: success slack notification 68 | uses: rtCamp/action-slack-notify@v2 69 | env: 70 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 71 | SLACK_ICON: https://github.com/actions.png?size=48 72 | SLACK_MESSAGE: "Built and pushed quay.io/konveyor/move2kube-ui:${{ steps.image_tag.outputs.tag }}" 73 | SLACK_TITLE: Success 74 | SLACK_USERNAME: GitHubActions 75 | - if: failure() 76 | name: failure slack notification 77 | uses: rtCamp/action-slack-notify@v2 78 | env: 79 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 80 | SLACK_COLOR: "#BD3232" 81 | SLACK_ICON: https://github.com/actions.png?size=48 82 | SLACK_MESSAGE: "Failed to build and push image quay.io/konveyor/move2kube-ui:${{ steps.image_tag.outputs.tag }}" 83 | SLACK_TITLE: Failed 84 | SLACK_USERNAME: GitHubActions -------------------------------------------------------------------------------- /.github/workflows/closepr.yml: -------------------------------------------------------------------------------- 1 | name: Close a PR raised by the Move2Kube bot account 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | pr_number: 7 | description: "The id of the pull request to close. Example: 3143 for https://github.com/k8s-operatorhub/community-operators/pull/3143" 8 | required: true 9 | repo_owner_and_name: 10 | description: "The repo on which to close the pull request. Example: 'redhat-openshift-ecosystem/community-operators-prod'" 11 | required: false 12 | default: 'k8s-operatorhub/community-operators' 13 | 14 | jobs: 15 | close_pr: 16 | name: Close an existing PR created by the bot account 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: close-a-pull-request 20 | run: | 21 | curl -L \ 22 | -X PATCH \ 23 | -H "Accept: application/vnd.github+json" \ 24 | -H 'Authorization: Bearer ${{ secrets.MOVE2KUBE_PATOKEN }}' \ 25 | -H "X-GitHub-Api-Version: 2022-11-28" \ 26 | https://api.github.com/repos/${{ github.event.inputs.repo_owner_and_name }}/pulls/${{ github.event.inputs.pr_number }} \ 27 | -d '{"state":"closed"}' 28 | -------------------------------------------------------------------------------- /.github/workflows/labelpr.yml: -------------------------------------------------------------------------------- 1 | name: Label PRs 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, edited, synchronize, reopened] 6 | 7 | jobs: 8 | label_pr: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/github-script@v3 12 | with: 13 | github-token: ${{ secrets.GITHUB_TOKEN }} 14 | script: | 15 | const pr_welcome_msg = `Thanks for making a pull request! 😃\nOne of the maintainers will review and advise on the next steps.`; 16 | // https://github.com/commitizen/conventional-commit-types 17 | const valid_pr_types = ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']; 18 | 19 | if(context.payload.pull_request.comments === 0) { 20 | await github.issues.createComment({ ...context.repo, issue_number: context.payload.number, body: pr_welcome_msg}); 21 | } 22 | 23 | const title = context.payload.pull_request.title; 24 | const results = /^(\w+)(\(\w+\))?!?:/.exec(title); 25 | if (results === null) return core.setFailed(`The title does not follow conventional commits spec: https://www.conventionalcommits.org/en/v1.0.0/#summary Title: ${title}`); 26 | 27 | const pr_type = results[1]; 28 | core.info(`pr_type: ${pr_type}`); 29 | 30 | if (!valid_pr_types.includes(pr_type)) return core.setFailed(`Unknown pull request type: ${pr_type}`); 31 | 32 | const labels = context.payload.pull_request.labels; 33 | const new_labels = labels.filter(label => !valid_pr_types.includes(label.name)); // keep all labels that are not in valid_pr_types 34 | new_labels.push({name: pr_type}); 35 | await github.issues.update({ ...context.repo, issue_number: context.payload.number, labels: new_labels }); -------------------------------------------------------------------------------- /.github/workflows/prbuild.yml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | - "release-*" 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | jobs: 14 | build: 15 | name: Build 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-node@v2-beta 20 | with: 21 | node-version: "16.16.0" 22 | - run: corepack enable 23 | - run: make install 24 | - run: make build -------------------------------------------------------------------------------- /.github/workflows/raise-operator-pr.yml: -------------------------------------------------------------------------------- 1 | name: Raise PR to Operator Hub community and prod repos 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: "current tag: The tag for this release" 8 | required: true 9 | default: v0.1.0-rc.2 10 | commit_ref: 11 | description: "commit ref: The branch or tag of the commit to use for the release." 12 | required: false 13 | default: main 14 | 15 | jobs: 16 | build_bundle_and_raise_pr: 17 | name: Build the latest bundle and raise 2 PRs 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: checkout-the-current-repo 21 | uses: actions/checkout@v2 22 | with: 23 | ref: ${{ github.event.inputs.commit_ref }} 24 | - name: install-yq 25 | run: | 26 | echo "installing yq..." 27 | curl -L https://github.com/mikefarah/yq/releases/download/v4.13.5/yq_linux_amd64 -o /usr/local/bin/yq && chmod +x /usr/local/bin/yq 28 | echo "yq installed" 29 | - id: get_channel 30 | uses: actions/github-script@v6 31 | with: 32 | result-encoding: string 33 | script: | 34 | const version = '${{ github.event.inputs.tag }}'; 35 | return /^v\d+\.\d+\.\d+-\w+/.test(version) ? 'prerelease' : 'stable'; 36 | - name: build the operator bundle and save to temp directory 37 | run: | 38 | cd operator/ || exit 1 39 | VERSION='${{ github.event.inputs.tag }}' CHANNELS='${{ steps.get_channel.outputs.result }}' DEFAULT_CHANNEL='stable' make bundle 40 | echo 'copy-bundle-into-tmp' 41 | cp -r bundle/ /tmp/bundle-operator-move2kube 42 | cp bundle.Dockerfile /tmp/bundle.Dockerfile 43 | - name: cleanup 44 | run: rm -rf {*,.*} || true 45 | - name: checkout-the-move2kube-fork-of-upstream-repo 46 | shell: bash 47 | run: | 48 | git clone 'https://move2kube:${{ secrets.MOVE2KUBE_PATOKEN }}@github.com/move2kube/community-operators' 49 | cd community-operators/ || exit 1 50 | git checkout main 51 | git config user.name move2kube 52 | git config user.email move2kube@gmail.com 53 | echo 'sync our fork with upstream' 54 | git remote add upstream https://github.com/k8s-operatorhub/community-operators 55 | git fetch --all 56 | git reset --hard upstream/main 57 | echo 'copy-bundle-into-fork-creating-new-version-dir' 58 | cd operators/move2kube-operator/ || exit 1 59 | VERSION='${{ github.event.inputs.tag }}' 60 | VERSION_WITHOUT_V="${VERSION#v}" 61 | cp -r /tmp/bundle-operator-move2kube "${VERSION_WITHOUT_V}" && cp /tmp/bundle.Dockerfile "${VERSION_WITHOUT_V}"/bundle.Dockerfile 62 | echo 'make a commit and push to our fork' 63 | # https://stackoverflow.com/questions/67789507/generate-unique-string-in-github-actions 64 | BRANCH_NAME='feat-${{ github.event.inputs.tag }}-${{ github.run_id }}-${{ github.run_attempt }}' 65 | git checkout -b "${BRANCH_NAME}" 66 | git add -A 67 | git commit -s -m 'operator move2kube-operator (${{ github.event.inputs.tag }})' 68 | git push -u origin "${BRANCH_NAME}" 69 | echo 'create-pull-request-to-community-repo' 70 | curl -L \ 71 | -X POST \ 72 | -H 'Accept: application/vnd.github+json' \ 73 | -H 'Authorization: Bearer ${{ secrets.MOVE2KUBE_PATOKEN }}' \ 74 | -H 'X-GitHub-Api-Version: 2022-11-28' \ 75 | https://api.github.com/repos/k8s-operatorhub/community-operators/pulls \ 76 | -d '{"title":"operator move2kube-operator (${{ github.event.inputs.tag }})","body":"An automated PR to update move2kube-operator to ${{ github.event.inputs.tag }}","head":"move2kube:'"${BRANCH_NAME}"'","base":"main"}' 77 | - name: checkout-the-move2kube-fork-of-the-prod-upstream-repo 78 | shell: bash 79 | run: | 80 | git clone 'https://move2kube:${{ secrets.MOVE2KUBE_PATOKEN }}@github.com/move2kube/community-operators-prod' 81 | cd community-operators-prod/ || exit 1 82 | git checkout main 83 | git config user.name move2kube 84 | git config user.email move2kube@gmail.com 85 | echo 'sync our fork with upstream' 86 | git remote add upstream https://github.com/redhat-openshift-ecosystem/community-operators-prod 87 | git fetch --all 88 | git reset --hard upstream/main 89 | echo 'copy-bundle-into-fork-of-prod-repo-creating-new-version-dir' 90 | cd operators/move2kube-operator/ || exit 1 91 | VERSION='${{ github.event.inputs.tag }}' 92 | VERSION_WITHOUT_V="${VERSION#v}" 93 | cp -r /tmp/bundle-operator-move2kube "${VERSION_WITHOUT_V}" && cp /tmp/bundle.Dockerfile "${VERSION_WITHOUT_V}"/bundle.Dockerfile 94 | echo 'make a commit and push to our fork' 95 | # https://stackoverflow.com/questions/67789507/generate-unique-string-in-github-actions 96 | BRANCH_NAME='feat-${{ github.event.inputs.tag }}-${{ github.run_id }}-${{ github.run_attempt }}' 97 | git checkout -b "${BRANCH_NAME}" 98 | git add -A 99 | git commit -s -m 'operator move2kube-operator (${{ github.event.inputs.tag }})' 100 | git push -u origin "${BRANCH_NAME}" 101 | echo 'create-pull-request-to-prod-repo' 102 | curl -L \ 103 | -X POST \ 104 | -H 'Accept: application/vnd.github+json' \ 105 | -H 'Authorization: Bearer ${{ secrets.MOVE2KUBE_PATOKEN }}' \ 106 | -H 'X-GitHub-Api-Version: 2022-11-28' \ 107 | https://api.github.com/repos/redhat-openshift-ecosystem/community-operators-prod/pulls \ 108 | -d '{"title":"operator move2kube-operator (${{ github.event.inputs.tag }})","body":"An automated PR to update move2kube-operator to ${{ github.event.inputs.tag }}","head":"move2kube:'"${BRANCH_NAME}"'","base":"main"}' 109 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Tag and Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: "current tag: The tag for this release" 8 | required: true 9 | default: v0.1.0-rc.2 10 | prev_tag: 11 | description: "previous tag: Tag from which to start calculating the changelog" 12 | required: true 13 | default: v0.1.0-beta.0 14 | commit_ref: 15 | description: "commit ref: The branch, tag or SHA of the commit to use for the release." 16 | required: false 17 | default: main 18 | 19 | defaults: 20 | run: 21 | shell: bash 22 | 23 | jobs: 24 | build: 25 | name: Build 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | with: 30 | ref: ${{ github.event.inputs.commit_ref }} 31 | - uses: actions/setup-node@v2-beta 32 | with: 33 | node-version: "16.16.0" 34 | - run: corepack enable 35 | - run: make install 36 | - run: make build 37 | - if: failure() 38 | uses: rtCamp/action-slack-notify@v2 39 | env: 40 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 41 | SLACK_COLOR: "#BD3232" 42 | SLACK_ICON: https://github.com/actions.png?size=48 43 | SLACK_MESSAGE: "Build and test failed for move2kube-ui on tag ${{ github.event.inputs.tag }}" 44 | SLACK_TITLE: Failed 45 | SLACK_USERNAME: GitHubActions 46 | 47 | tag: 48 | needs: [build] 49 | name: Tag 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v2 53 | with: 54 | ref: ${{ github.event.inputs.commit_ref }} 55 | - id: get_sha 56 | run: | 57 | echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT 58 | - uses: actions/github-script@v3 59 | with: 60 | github-token: ${{ secrets.MOVE2KUBE_PATOKEN }} 61 | script: | 62 | const tag = '${{ github.event.inputs.tag }}'; 63 | const sha = '${{ steps.get_sha.outputs.sha }}'; 64 | 65 | let tag_exists = false; 66 | try { 67 | const resp = await github.git.getRef({...context.repo, ref: `tags/${tag}`}); 68 | tag_exists = true; 69 | core.info(`the tag ${tag} already exists on ${resp.data.object.type} ${resp.data.object.sha}`); 70 | } catch(err) { 71 | if(err.status !== 404 && err.status !== '404'){ 72 | throw err; 73 | } 74 | } 75 | if(tag_exists) { 76 | core.info(`deleting the tag ${tag}`); 77 | const resp = await github.git.deleteRef({...context.repo, ref: `tags/${tag}`}); 78 | } 79 | 80 | core.info(`creating the tag ${tag} on the commit ${sha}`); 81 | // create the tag 82 | github.git.createRef({ 83 | ...context.repo, 84 | ref: `refs/tags/${tag}`, 85 | sha 86 | }); 87 | if(!tag.endsWith('-beta.0')) { 88 | return; 89 | } 90 | // create the release branch 91 | const major_minor = /^v(\d+\.\d+)/.exec(tag); 92 | if(!major_minor || major_minor.length !== 2){ 93 | return core.setFailed(`The tag is not a valid semantic version. tag: ${tag}`); 94 | } 95 | const branch_name = `release-${major_minor[1]}`; 96 | github.git.createRef({ 97 | ...context.repo, 98 | ref: `refs/heads/${branch_name}`, 99 | sha 100 | }); 101 | 102 | create_release_draft: 103 | needs: [tag] 104 | name: Create release draft 105 | runs-on: ubuntu-latest 106 | steps: 107 | - uses: actions/checkout@v2 108 | with: 109 | ref: ${{ github.event.inputs.commit_ref }} 110 | fetch-depth: 0 111 | - name: create release draft 112 | uses: konveyor/move2kube-create-release-draft@v1 113 | with: 114 | token: ${{ secrets.GITHUB_TOKEN }} 115 | tag: ${{ github.event.inputs.tag }} 116 | prev_tag: ${{ github.event.inputs.prev_tag }} 117 | config: releasenotes-config.js 118 | - uses: azure/setup-helm@v1 119 | - run: IMAGE_TAG='${{ github.event.inputs.tag }}' make prepare-for-release 120 | - run: helm package helm-charts/move2kube 121 | - run: mkdir temp/ 122 | - run: mv *.tgz temp/ 123 | - run: curl -L -o oldindex.yaml https://move2kube.konveyor.io/index.yaml 124 | - run: helm repo index --merge oldindex.yaml --url https://github.com/konveyor/move2kube-ui/releases/download/${{ github.event.inputs.tag }} temp/ 125 | - uses: konveyor/move2kube-upload-release-action@v3.0.1 126 | with: 127 | repo_token: ${{ secrets.GITHUB_TOKEN }} 128 | tag: ${{ github.event.inputs.tag }} 129 | file: temp/* 130 | file_glob: true 131 | overwrite: true 132 | - name: slack notification 133 | uses: rtCamp/action-slack-notify@v2 134 | env: 135 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 136 | SLACK_ICON: https://github.com/actions.png?size=48 137 | SLACK_MESSAGE: "Release draft for move2kube-ui ${{ github.event.inputs.tag }} created: https://github.com/konveyor/move2kube-ui/releases" 138 | SLACK_TITLE: Success 139 | SLACK_USERNAME: GitHubActions 140 | 141 | image_build: 142 | needs: [tag] 143 | name: Image build 144 | runs-on: ubuntu-latest 145 | steps: 146 | - uses: actions/checkout@v2 147 | with: 148 | ref: ${{ github.event.inputs.commit_ref }} 149 | - name: pull latest image to reuse layers 150 | run: | 151 | docker pull quay.io/konveyor/move2kube-ui:latest || true 152 | docker pull quay.io/konveyor/move2kube-ui-builder:latest || true 153 | - run: echo "${{ secrets.QUAY_BOT_PASSWORD }}" | docker login --username "${{ secrets.QUAY_BOT_USERNAME }}" --password-stdin quay.io 154 | - name: Set up QEMU 155 | uses: docker/setup-qemu-action@v2 156 | - name: Set up Docker Buildx 157 | id: buildx 158 | uses: docker/setup-buildx-action@v2 159 | - name: build container image 160 | run: VERSION='${{ github.event.inputs.tag }}' make cmultibuildpush 161 | # build the operator bundle and container images 162 | - name: install-yq 163 | run: | 164 | echo "installing yq..." 165 | curl -L https://github.com/mikefarah/yq/releases/download/v4.13.5/yq_linux_amd64 -o /usr/local/bin/yq && chmod +x /usr/local/bin/yq 166 | echo "yq installed" 167 | - id: get_channel 168 | uses: actions/github-script@v6 169 | with: 170 | result-encoding: string 171 | script: | 172 | const version = '${{ github.event.inputs.tag }}'; 173 | return /^v\d+\.\d+\.\d+-\w+/.test(version) ? 'prerelease' : 'stable'; 174 | - name: build the operator bundle and container images and push the images to quay 175 | run: | 176 | cd operator/ || exit 1 177 | VERSION='${{ github.event.inputs.tag }}' make docker-build 178 | VERSION='${{ github.event.inputs.tag }}' make docker-push 179 | VERSION='${{ github.event.inputs.tag }}' CHANNELS='${{ steps.get_channel.outputs.result }}' DEFAULT_CHANNEL='stable' make bundle 180 | VERSION='${{ github.event.inputs.tag }}' make bundle-build 181 | VERSION='${{ github.event.inputs.tag }}' make bundle-push 182 | # build the operator bundle and container images 183 | - name: success slack notification 184 | uses: rtCamp/action-slack-notify@v2 185 | env: 186 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 187 | SLACK_ICON: https://github.com/actions.png?size=48 188 | SLACK_MESSAGE: "Built and pushed quay.io/konveyor/move2kube-ui:${{ github.event.inputs.tag }}" 189 | SLACK_TITLE: Success 190 | SLACK_USERNAME: GitHubActions 191 | - if: failure() 192 | name: failure slack notification 193 | uses: rtCamp/action-slack-notify@v2 194 | env: 195 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 196 | SLACK_COLOR: "#BD3232" 197 | SLACK_ICON: https://github.com/actions.png?size=48 198 | SLACK_MESSAGE: "Failed to build and push image quay.io/konveyor/move2kube-ui:${{ github.event.inputs.tag }}" 199 | SLACK_TITLE: Failed 200 | SLACK_USERNAME: GitHubActions 201 | 202 | update_draft_title: 203 | needs: [create_release_draft, image_build] 204 | runs-on: ubuntu-latest 205 | steps: 206 | - uses: actions/github-script@v3 207 | with: 208 | github-token: ${{ secrets.MOVE2KUBE_PATOKEN }} 209 | script: | 210 | const tag = '${{ github.event.inputs.tag }}'; 211 | const response = await github.repos.listReleases({ ...context.repo }); 212 | const drafts = response.data.filter(release => release.draft && release.tag_name === tag); 213 | if(drafts.length !== 1) { 214 | return core.setFailed(`Expected to find exactly one draft release with the tag ${tag}. Found: ${drafts.length}`); 215 | } 216 | const draft = drafts[0]; 217 | if(!draft.name.startsWith('[WIP] ')) { 218 | return core.setFailed(`Expected the draft name to begin with [WIP]. Found: ${draft.name}`); 219 | } 220 | const new_name = draft.name.replace(/^\[WIP\] /, ''); 221 | await github.repos.updateRelease({...context.repo, release_id: draft.id, name: new_name, tag_name: draft.tag_name}); -------------------------------------------------------------------------------- /.github/workflows/tag.yml: -------------------------------------------------------------------------------- 1 | name: Tag 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: "Tag name" 8 | required: true 9 | default: "v0.1.0-rc.2" 10 | sha: 11 | description: "SHA of the commit to tag. Defaults to latest commit on branch this was invoked on." 12 | required: false 13 | 14 | jobs: 15 | tag: 16 | name: Tag 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/github-script@v3 20 | with: 21 | github-token: ${{ secrets.MOVE2KUBE_PATOKEN }} 22 | script: | 23 | const sha = '${{ github.event.inputs.sha }}' || context.sha; 24 | await github.git.createRef({ 25 | ...context.repo, 26 | ref: 'refs/tags/${{ github.event.inputs.tag }}', 27 | sha 28 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corporation 2023 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # dependencies 16 | /node_modules 17 | /.pnp 18 | .pnp.js 19 | 20 | # testing 21 | /coverage 22 | 23 | # production 24 | /build 25 | 26 | # misc 27 | .DS_Store 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | npm-debug.log* 34 | yarn-debug.log* 35 | yarn-error.log* 36 | 37 | /data/ 38 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corporation 2023 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ARG VERSION=latest 16 | 17 | ### Builder Image ### 18 | FROM registry.access.redhat.com/ubi8/nodejs-16-minimal:1-48 as build_base 19 | USER root:root 20 | WORKDIR /app 21 | RUN corepack enable 22 | # install dependencies 23 | COPY package.json pnpm-lock.yaml ./ 24 | RUN pnpm install 25 | # copy sources and build the project 26 | COPY . . 27 | RUN pnpm run build 28 | 29 | ### Runner Image ### 30 | FROM quay.io/konveyor/move2kube-api:${VERSION} 31 | ARG VERSION=latest 32 | ARG MOVE2KUBE_UI_VERSION 33 | ARG MOVE2KUBE_UI_GIT_COMMIT_HASH 34 | ARG MOVE2KUBE_UI_GIT_TREE_STATUS 35 | ENV MOVE2KUBE_UI_VERSION="${VERSION}" 36 | ENV MOVE2KUBE_UI_GIT_COMMIT_HASH="${MOVE2KUBE_UI_GIT_COMMIT_HASH}" 37 | ENV MOVE2KUBE_UI_GIT_TREE_STATUS="${MOVE2KUBE_UI_GIT_TREE_STATUS}" 38 | 39 | # copy build output 40 | COPY --from=build_base /app/build ./build 41 | CMD ["move2kube-api", "--port", "8080", "--log-level", "info", "--static-files-dir", "build"] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corporation 2023 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | BINNAME ?= move2kube-ui 16 | REGISTRYNS := quay.io/konveyor 17 | DISTDIR := $(CURDIR)/build 18 | 19 | GIT_COMMIT = $(shell git rev-parse HEAD) 20 | GIT_SHA = $(shell git rev-parse --short HEAD) 21 | GIT_TAG = $(shell git tag --points-at | tail -n 1) 22 | GIT_DIRTY = $(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean") 23 | 24 | MULTI_ARCH_TARGET_PLATFORMS := linux/arm64,linux/amd64 25 | 26 | ifdef VERSION 27 | BINARY_VERSION = $(VERSION) 28 | endif 29 | BINARY_VERSION ?= ${GIT_TAG} 30 | ifneq ($(BINARY_VERSION),) 31 | VERSION ?= $(BINARY_VERSION) 32 | endif 33 | 34 | VERSION ?= latest 35 | 36 | VERSION_METADATA = unreleased 37 | ifneq ($(GIT_TAG),) 38 | VERSION_METADATA = 39 | endif 40 | 41 | # Setting container tool 42 | DOCKER_CMD := $(shell command -v docker 2> /dev/null) 43 | PODMAN_CMD := $(shell command -v podman 2> /dev/null) 44 | 45 | ifdef DOCKER_CMD 46 | CONTAINER_TOOL = 'docker' 47 | else ifdef PODMAN_CMD 48 | CONTAINER_TOOL = 'podman' 49 | endif 50 | 51 | # HELP 52 | # This will output the help for each task 53 | .PHONY: help 54 | help: ## This help. 55 | @awk 'BEGIN {FS = ":.*?## "} /^[0-9a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 56 | 57 | .PHONY: clean 58 | clean: 59 | rm -rf $(DISTDIR) 60 | 61 | .PHONY: install 62 | install: ## Install dependencies 63 | @pnpm install 64 | 65 | .PHONY: build 66 | build: ## Build application 67 | @pnpm run build 68 | 69 | .PHONY: start 70 | start: install build ## Start server 71 | @pnpm run start-api 72 | 73 | # -- Container Runtime -- 74 | 75 | .PHONY: cbuild 76 | cbuild: ## Build container image 77 | ifndef CONTAINER_TOOL 78 | $(error No container tool (docker, podman) found in your environment. Please, install one) 79 | endif 80 | @echo "Building image with $(CONTAINER_TOOL)" 81 | ${CONTAINER_TOOL} build -t ${REGISTRYNS}/${BINNAME}-builder:${VERSION} --cache-from ${REGISTRYNS}/${BINNAME}-builder:latest --target build_base --build-arg VERSION=${VERSION} . 82 | ${CONTAINER_TOOL} tag ${REGISTRYNS}/${BINNAME}-builder:${VERSION} ${REGISTRYNS}/${BINNAME}-builder:latest 83 | ${CONTAINER_TOOL} build -t ${REGISTRYNS}/${BINNAME}:${VERSION} --cache-from ${REGISTRYNS}/${BINNAME}-builder:latest --cache-from ${REGISTRYNS}/${BINNAME}:latest --build-arg VERSION=${VERSION} --build-arg "MOVE2KUBE_UI_GIT_COMMIT_HASH=${GIT_COMMIT}" --build-arg "MOVE2KUBE_UI_GIT_TREE_STATUS=${GIT_DIRTY}" . 84 | ${CONTAINER_TOOL} tag ${REGISTRYNS}/${BINNAME}:${VERSION} ${REGISTRYNS}/${BINNAME}:latest 85 | 86 | .PHONY: cpush 87 | cpush: ## Push container image 88 | ifndef CONTAINER_TOOL 89 | $(error No container tool (docker, podman) found in your environment. Please, install one) 90 | endif 91 | @echo "Pushing image with $(CONTAINER_TOOL)" 92 | # To help with reusing layers and hence speeding up build 93 | ${CONTAINER_TOOL} push ${REGISTRYNS}/${BINNAME}-builder:${VERSION} 94 | ${CONTAINER_TOOL} push ${REGISTRYNS}/${BINNAME}:${VERSION} 95 | 96 | .PHONY: crun 97 | crun: ## Run using container image 98 | ifndef CONTAINER_TOOL 99 | $(error No container tool (docker, podman) found in your environment. Please, install one) 100 | endif 101 | @echo "Running image with $(CONTAINER_TOOL)" 102 | ifdef DOCKER_CMD 103 | ${CONTAINER_TOOL} run --rm -it -p 8080:8080 -v ${PWD}/data:/move2kube-api/data -v /var/run/docker.sock:/var/run/docker.sock quay.io/konveyor/move2kube-ui:latest 104 | else 105 | ${CONTAINER_TOOL} run --rm -it -p 8080:8080 -v ${PWD}/data:/move2kube-api/data:z --network=bridge quay.io/konveyor/move2kube-ui:latest 106 | endif 107 | 108 | .PHONY: prepare-for-release 109 | prepare-for-release: 110 | mv helm-charts/move2kube/Chart.yaml old 111 | cat old | sed -E s/version:\ v0.1.0-unreleased/version:\ ${IMAGE_TAG}/ | sed -E s/appVersion:\ v0.1.0-unreleased/appVersion:\ ${IMAGE_TAG}/ > helm-charts/move2kube/Chart.yaml 112 | rm old 113 | 114 | .PHONY: cmultibuildpush 115 | cmultibuildpush: ## Build and push multi arch container image 116 | ifndef DOCKER_CMD 117 | $(error Docker wasn't detected. Please install docker and try again.) 118 | endif 119 | @echo "Building image for multiple architectures with $(CONTAINER_TOOL)" 120 | 121 | ## TODO: When docker exporter supports exporting manifest lists we can separate out this into two steps: build and push 122 | 123 | ${CONTAINER_TOOL} buildx create --name m2k-builder --driver-opt network=host --use --platform ${MULTI_ARCH_TARGET_PLATFORMS} 124 | 125 | ${CONTAINER_TOOL} buildx build --platform ${MULTI_ARCH_TARGET_PLATFORMS} --tag ${REGISTRYNS}/${BINNAME}-builder:${VERSION} --tag ${REGISTRYNS}/${BINNAME}-builder:latest --cache-from ${REGISTRYNS}/${BINNAME}-builder:latest --target build_base --build-arg VERSION=${VERSION} --push .; 126 | ${CONTAINER_TOOL} buildx build --platform ${MULTI_ARCH_TARGET_PLATFORMS} --tag ${REGISTRYNS}/${BINNAME}:${VERSION} --tag ${REGISTRYNS}/${BINNAME}:latest --cache-from ${REGISTRYNS}/${BINNAME}-builder:latest --cache-from ${REGISTRYNS}/${BINNAME}:latest --build-arg VERSION=${VERSION} --build-arg "MOVE2KUBE_UI_GIT_COMMIT_HASH=${GIT_COMMIT}" --build-arg "MOVE2KUBE_UI_GIT_TREE_STATUS=${GIT_DIRTY}" --push .; 127 | 128 | ${CONTAINER_TOOL} buildx rm m2k-builder -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/konveyor/move2kube-ui/workflows/Build/badge.svg 'Github Actions')](https://github.com/konveyor/move2kube-ui/actions?query=workflow%3ABuild) 2 | [![Docker Repository on Quay](https://quay.io/repository/konveyor/move2kube-ui/status 'Docker Repository on Quay')](https://quay.io/repository/konveyor/move2kube-ui) 3 | [![License](https://img.shields.io/:license-apache-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 4 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/konveyor/move2kube-ui/pulls) 5 | [](https://kubernetes.slack.com/archives/CR85S82A2) 6 | 7 | # Move2Kube-UI 8 | 9 | An UI for interacting with [Move2Kube API](https://github.com/konveyor/move2kube-api). 10 | 11 | ## Starting the UI 12 | 13 | ### As a container 14 | 15 | ```shell 16 | $ docker run --rm -it -p 8080:8080 move2kube-ui 17 | ``` 18 | 19 | ### As a compose app 20 | 21 | Using `docker`: 22 | 23 | ```shell 24 | $ docker-compose up 25 | ``` 26 | 27 | or using `podman`: 28 | 29 | ```shell 30 | $ podman-compose -f podman-compose.yml up 31 | ``` 32 | 33 | ### As a helm chart 34 | 35 | Make sure you are logged into a Kubernetes/Openshift cluster 36 | 37 | ```shell 38 | $ cd helm-charts 39 | $ helm install my-m2k-instance ./move2kube/ 40 | ``` 41 | 42 | For more information and configuration options, see [Move2Kube UI Helm chart](https://github.com/konveyor/move2kube-ui/blob/main/helm-charts/move2kube/README.md) 43 | 44 | ### As an operator 45 | 46 | For deploying the UI as a Kubernetes Operator, see [Move2Kube Operator](https://github.com/konveyor/move2kube-operator) 47 | 48 | ## Development 49 | 50 | ### Running without a container 51 | 52 | This requires Golang 1.18 and NodeJS 16 and above. 53 | 54 | 1. Install the Move2Kube CLI tool https://github.com/konveyor/move2kube 55 | 1. Install the Move2Kube API server https://github.com/konveyor/move2kube-api 56 | 1. `make install` 57 | 1. `make build` 58 | 1. `pnpm run start` 59 | 60 | ### Building the container image from source 61 | 62 | 1. Build the UI image: `make cbuild` 63 | 1. Create a data folder to persist information: `mkdir -p data` 64 | 1. This uses `docker` or `podman` and runs everything inside a single container using the UI image: `make crun` 65 | 1. Access the UI at http://localhost:8080 66 | 67 | ## Discussion 68 | 69 | For any questions reach out to us on any of the communication channels given on our website https://move2kube.konveyor.io/ 70 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [slack](https://kubernetes.slack.com/archives/CR85S82A2) in 65 | [kubernetes](https://slack.k8s.io/) workspace. 66 | All complaints will be reviewed and investigated promptly and fairly. 67 | 68 | All community leaders are obligated to respect the privacy and security of the 69 | reporter of any incident. 70 | 71 | ## Enforcement Guidelines 72 | 73 | Community leaders will follow these Community Impact Guidelines in determining 74 | the consequences for any action they deem in violation of this Code of Conduct: 75 | 76 | ### 1. Correction 77 | 78 | **Community Impact**: Use of inappropriate language or other behavior deemed 79 | unprofessional or unwelcome in the community. 80 | 81 | **Consequence**: A private, written warning from community leaders, providing 82 | clarity around the nature of the violation and an explanation of why the 83 | behavior was inappropriate. A public apology may be requested. 84 | 85 | ### 2. Warning 86 | 87 | **Community Impact**: A violation through a single incident or series 88 | of actions. 89 | 90 | **Consequence**: A warning with consequences for continued behavior. No 91 | interaction with the people involved, including unsolicited interaction with 92 | those enforcing the Code of Conduct, for a specified period of time. This 93 | includes avoiding interactions in community spaces as well as external channels 94 | like social media. Violating these terms may lead to a temporary or 95 | permanent ban. 96 | 97 | ### 3. Temporary Ban 98 | 99 | **Community Impact**: A serious violation of community standards, including 100 | sustained inappropriate behavior. 101 | 102 | **Consequence**: A temporary ban from any sort of interaction or public 103 | communication with the community for a specified period of time. No public or 104 | private interaction with the people involved, including unsolicited interaction 105 | with those enforcing the Code of Conduct, is allowed during this period. 106 | Violating these terms may lead to a permanent ban. 107 | 108 | ### 4. Permanent Ban 109 | 110 | **Community Impact**: Demonstrating a pattern of violation of community 111 | standards, including sustained inappropriate behavior, harassment of an 112 | individual, or aggression toward or disparagement of classes of individuals. 113 | 114 | **Consequence**: A permanent ban from any sort of public interaction within 115 | the community. 116 | 117 | ## Attribution 118 | 119 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 120 | version 2.0, available at 121 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 122 | 123 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 124 | enforcement ladder](https://github.com/mozilla/diversity). 125 | 126 | [homepage]: https://www.contributor-covenant.org 127 | 128 | For answers to common questions about this code of conduct, see the FAQ at 129 | https://www.contributor-covenant.org/faq. Translations are available at 130 | https://www.contributor-covenant.org/translations. 131 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please read our code of conduct before contributing and make sure to follow it in all interactions with the project. 4 | 5 | If your proposed feature requires extensive changes/additions please contact us or raise a Github issue first. 6 | 7 | In order to contribute please follow this process: 8 | 9 | 1. Fork the repo on github and clone your fork. 10 | 2. Make a new branch for your feature/bug fix. Example: `git checkout -b myfeature` 11 | 3. Make your changes and commit. 12 | - Note: Please run `make test-style` and `make test` before making any commits to run the linters and ensure they pass build and test. This requirement allows the use of `git bisect` to find the exact commit that introduced a specific bug. 13 | 4. Make sure to format your code properly (`go fmt`) and update any relevant documentation, README.md, etc. about the changes you made. 14 | - Note: If it is a new feature please add unit tests for the same. If it is a bug fix please add tests/test cases to catch regressions in the future. 15 | 16 | ## Pull Request Process 17 | 18 | Once you are ready to have your work merged into the main repo follow these steps: 19 | 20 | 1. Fetch the latest commits from upstream. `git fetch upstream` 21 | 2. Rebase the commits from your branch onto the main branch. `git rebase upstream/main` 22 | - Note: You will need to fix any merge conflicts that occur. 23 | 3. Once all conflicts have been resolved, push the commits to your fork (`git push`) and submit a pull request on Github. 24 | 4. The pull request may be merged after CI checks have passed and at least one maintainer has signed off on it. 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | move2kubeui: 4 | build: 5 | context: ./ 6 | dockerfile: Dockerfile 7 | image: quay.io/konveyor/move2kube-ui:latest 8 | ports: 9 | - "8080:8080" 10 | volumes: 11 | - /var/run/docker.sock:/var/run/docker.sock 12 | - "./data:/move2kube-api/data" -------------------------------------------------------------------------------- /helm-charts/move2kube/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | description: A generated Helm Chart for move2kube 3 | keywords: 4 | - move2kube 5 | name: move2kube 6 | version: v0.1.0-unreleased 7 | appVersion: v0.1.0-unreleased 8 | type: application 9 | icon: https://raw.githubusercontent.com/konveyor/community/main/brand/logo/konveyor-logo-move2kube.svg 10 | -------------------------------------------------------------------------------- /helm-charts/move2kube/README.md: -------------------------------------------------------------------------------- 1 | # Move2Kube Helm Chart 2 | 3 | Move2Kube helps speed up the journey to Kubernetes. 4 | 5 | ## Prerequisites 6 | 7 | - Kubectl 8 | - Helm 3 9 | - A Kubernetes cluster 10 | 11 | ## Usage 12 | 13 | ### Helm chart 14 | 15 | 1. Configure kubectl to contact the K8s API server for your cluster. 16 | 2. Create a namespace and configure kubectl to use it: 17 | ``` 18 | kubectl create namespace my-namespace 19 | kubectl config set-context --current --namespace my-namespace 20 | ``` 21 | 3. Add this helm repo: 22 | ``` 23 | helm repo add move2kube https://move2kube.konveyor.io 24 | helm repo update 25 | ``` 26 | 4. To install without authentication and authorization run: 27 | ``` 28 | helm install --set ingress.host='my.k8s.cluster.domain.com' my-move2kube move2kube/move2kube 29 | ``` 30 | Replace `my.k8s.cluster.domain.com` with the domain where you K8s cluster is deployed. 31 | 32 | If you need authentication and authorization then put the required details in a JSON file and run: 33 | ``` 34 | helm install \ 35 | --set ingress.host='my.k8s.cluster.domain.com' \ 36 | --set ingress.tlsSecretName='my-tls-secret' \ 37 | --set deployment.authServer.enable=true \ 38 | --set deployment.database.enable=true \ 39 | --set secret.api.enable=true \ 40 | --set-file 'secret.api.configYAML=path/to/my/config.yaml' \ 41 | --set-file 'secret.authServer.realmJSON=path/to/my/realm.json' \ 42 | --set-file 'secret.authServer.standaloneHAXML=path/to/my/standalone-ha.xml' \ 43 | my-move2kube move2kube/move2kube 44 | ``` 45 | Replace `my-tls-secret` with the name of the K8s secret that contains the certificate and private key required for TLS. 46 | Replace `path/to/my/config.yaml` with the path of a YAML file containing the config for the API server. 47 | Replace `path/to/my/realm.json` with the path of a JSON file containing the config for the Authorization server. 48 | Replace `path/to/my/standalone-ha.xml` with the path of an XML file containing the config for the Authorization server. 49 | 50 | 5. The helm chart will output the URL where you can access Move2Kube. 51 | You can also do `kubectl get ingress` to get find the url. 52 | Example: `https://my.k8s.cluster.domain.com` 53 | 54 | ## Discussion 55 | 56 | * For any questions reach out to us on any of the communication channels given on our website https://move2kube.konveyor.io/. 57 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Thank you for installing {{ .Chart.Name }}! 2 | 3 | Your release is named {{ .Release.Name }} and is accessible at: 4 | {{- if or (.Values.ingress.tlsSecret) (.Values.ingress.preferRoute) }} 5 | https://{{ .Values.ingress.host }} 6 | {{- else }} 7 | http://{{ .Values.ingress.host }} 8 | {{- end }} 9 | 10 | To learn more about the release, try: 11 | 12 | $ helm status {{ .Release.Name }} 13 | $ helm get all {{ .Release.Name }} 14 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/auth-server-deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.deployment.authServer.enable }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: "{{ .Release.Name }}-auth-server" 6 | labels: 7 | move2kube.konveyor.io/service: "{{ .Release.Name }}-auth-server" 8 | spec: 9 | replicas: {{ .Values.deployment.authServer.replicas }} 10 | selector: 11 | matchLabels: 12 | move2kube.konveyor.io/service: "{{ .Release.Name }}-auth-server" 13 | template: 14 | metadata: 15 | name: "{{ .Release.Name }}-auth-server" 16 | labels: 17 | move2kube.konveyor.io/service: "{{ .Release.Name }}-auth-server" 18 | spec: 19 | containers: 20 | - image: quay.io/keycloak/keycloak:15.0.2 21 | args: 22 | - "-Dnashorn.args=--language=es6" 23 | - "-Dkeycloak.profile.feature.upload_scripts=enabled" 24 | - "-Dkeycloak.frontendUrl=https://{{ .Values.ingress.host }}/auth-server" 25 | - "-Dkeycloak.adminUrl=http://{{ .Release.Name }}-auth-server:8081/auth-server" 26 | - "-Dkeycloak.myDatabaseUsername={{ .Values.secret.authServer.databaseUsername }}" 27 | - "-Dkeycloak.myDatabasePassword={{ .Values.secret.authServer.databasePassword }}" 28 | - "-Dkeycloak.myDatabaseHost={{ .Release.Name }}-database" 29 | imagePullPolicy: Always 30 | name: auth-server 31 | env: 32 | - name: KEYCLOAK_IMPORT 33 | value: /tmp/realm.json 34 | - name: KEYCLOAK_USER 35 | value: "{{ .Values.secret.authServer.adminUsername }}" 36 | - name: KEYCLOAK_PASSWORD 37 | value: "{{ .Values.secret.authServer.adminPassword }}" 38 | - name: DB_VENDOR 39 | value: postgres 40 | - name: DB_ADDR 41 | value: "{{ .Release.Name }}-database" 42 | - name: DB_PORT 43 | value: "5432" 44 | ports: 45 | - containerPort: 8080 46 | protocol: TCP 47 | volumeMounts: 48 | - name: auth-server-config 49 | mountPath: /tmp/realm.json 50 | subPath: realm.json 51 | readOnly: true 52 | - name: auth-server-config 53 | mountPath: /opt/jboss/keycloak/standalone/configuration/standalone-ha.xml 54 | subPath: standalone-ha.xml 55 | readOnly: true 56 | resources: 57 | limits: 58 | memory: "{{ .Values.deployment.authServer.memory }}" 59 | cpu: "{{ .Values.deployment.authServer.cpu }}" 60 | restartPolicy: Always 61 | volumes: 62 | - name: auth-server-config 63 | secret: 64 | secretName: '{{ .Values.secret.authServer.name | default (print .Release.Name "-auth-server") }}' 65 | {{- end }} 66 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/auth-server-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.deployment.authServer.enable) (.Values.secret.authServer.createNew) }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: '{{ .Values.secret.authServer.name | default (print .Release.Name "-auth-server") }}' 6 | data: 7 | realm.json: "{{ .Values.secret.authServer.realmJSON | toString | b64enc }}" 8 | standalone-ha.xml: "{{ .Values.secret.authServer.standaloneHAXML | toString | b64enc }}" 9 | {{- end }} 10 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/auth-server-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.deployment.authServer.enable }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: "{{ .Release.Name }}-auth-server" 6 | labels: 7 | move2kube.konveyor.io/service: "{{ .Release.Name }}-auth-server" 8 | spec: 9 | type: ClusterIP 10 | ports: 11 | - name: "port-8081" 12 | port: 8081 13 | targetPort: 8080 14 | selector: 15 | move2kube.konveyor.io/service: "{{ .Release.Name }}-auth-server" 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/database-deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.deployment.database.enable }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: "{{ .Release.Name }}-database" 6 | labels: 7 | move2kube.konveyor.io/service: "{{ .Release.Name }}-database" 8 | spec: 9 | replicas: {{ .Values.deployment.database.replicas }} 10 | selector: 11 | matchLabels: 12 | move2kube.konveyor.io/service: "{{ .Release.Name }}-database" 13 | template: 14 | metadata: 15 | name: "{{ .Release.Name }}-database" 16 | labels: 17 | move2kube.konveyor.io/service: "{{ .Release.Name }}-database" 18 | spec: 19 | {{- if .Values.deployment.database.startEmpty }} 20 | initContainers: 21 | - name: clear-data 22 | image: quay.io/konveyor/busybox 23 | command: ["/bin/sh", "-c"] 24 | args: 25 | - rm -rf /var/lib/postgresql/data/* 26 | volumeMounts: 27 | - name: common-volume 28 | mountPath: /var/lib/postgresql/data 29 | subPath: database/data 30 | {{- end }} 31 | containers: 32 | - image: postgres:13.4 33 | imagePullPolicy: Always 34 | name: database 35 | env: 36 | - name: PGDATA 37 | value: /var/lib/postgresql/data/auth-server 38 | - name: POSTGRES_DB 39 | value: auth-server 40 | - name: POSTGRES_USER 41 | value: "{{ .Values.secret.authServer.databaseUsername }}" 42 | - name: POSTGRES_PASSWORD 43 | value: "{{ .Values.secret.authServer.databasePassword }}" 44 | ports: 45 | - containerPort: 5432 46 | protocol: TCP 47 | resources: 48 | limits: 49 | memory: "{{ .Values.deployment.database.memory }}" 50 | cpu: "{{ .Values.deployment.database.cpu }}" 51 | volumeMounts: 52 | - name: common-volume 53 | mountPath: /var/lib/postgresql/data 54 | subPath: database/data 55 | restartPolicy: Always 56 | volumes: 57 | - name: common-volume 58 | {{- if .Values.persistentVolumeClaim.enable }} 59 | persistentVolumeClaim: 60 | claimName: '{{ .Values.persistentVolumeClaim.name | default (print .Release.Name "-move2kubeapi") }}' 61 | {{- else }} 62 | emptyDir: {} 63 | {{- end }} 64 | {{- end }} 65 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/database-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.deployment.database.enable }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: "{{ .Release.Name }}-database" 6 | spec: 7 | type: ClusterIP 8 | ports: 9 | - name: "port-5432" 10 | port: 5432 11 | targetPort: 5432 12 | selector: 13 | move2kube.konveyor.io/service: "{{ .Release.Name }}-database" 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/move2kube-api-headless-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: "{{ .Release.Name }}-move2kubeapiheadless" 5 | spec: 6 | type: ClusterIP 7 | clusterIP: None 8 | selector: 9 | move2kube.konveyor.io/service: "{{ .Release.Name }}-move2kubeapi" 10 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/move2kube-api-ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.ingress.enable) (not .Values.ingress.preferRoute) }} 2 | {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1/Ingress" }} 3 | apiVersion: networking.k8s.io/v1 4 | {{- else }} 5 | apiVersion: networking.k8s.io/v1beta1 6 | {{- end }} 7 | kind: Ingress 8 | metadata: 9 | name: "{{ .Release.Name }}-move2kubeapi" 10 | labels: 11 | move2kube.konveyor.io/service: "{{ .Release.Name }}-move2kubeapi" 12 | spec: 13 | {{- if .Values.ingress.tlsSecretName }} 14 | tls: 15 | - hosts: 16 | - "{{ .Values.ingress.host }}" 17 | secretName: "{{ .Values.ingress.tlsSecretName }}" 18 | {{- end }} 19 | rules: 20 | - host: "{{ .Values.ingress.host }}" 21 | http: 22 | paths: 23 | - path: / 24 | {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1/Ingress" }} 25 | pathType: Prefix 26 | backend: 27 | service: 28 | name: "{{ .Release.Name }}-move2kubeapi" 29 | port: 30 | number: 8080 31 | {{- else }} 32 | backend: 33 | serviceName: "{{ .Release.Name }}-move2kubeapi" 34 | servicePort: 8080 35 | {{- end }} 36 | {{- end }} 37 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/move2kube-api-route.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.ingress.enable) (.Values.ingress.preferRoute) }} 2 | apiVersion: route.openshift.io/v1 3 | kind: Route 4 | metadata: 5 | name: "{{ .Release.Name }}-move2kubeapi" 6 | labels: 7 | move2kube.konveyor.io/service: "{{ .Release.Name }}-move2kubeapi" 8 | spec: 9 | tls: 10 | termination: edge 11 | host: "{{ .Values.ingress.host }}" 12 | path: / 13 | to: 14 | kind: Service 15 | name: "{{ .Release.Name }}-move2kubeapi" 16 | port: 17 | targetPort: 8080 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/move2kube-api-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.secret.api.enable) (.Values.secret.api.createNew) }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: '{{ .Values.secret.api.name | default (print .Release.Name "-move2kubeapi") }}' 6 | data: 7 | config.yaml: "{{ .Values.secret.api.configYAML | toString | b64enc }}" 8 | {{- end }} 9 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/move2kube-api-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: "{{ .Release.Name }}-move2kubeapi" 5 | spec: 6 | type: ClusterIP 7 | ports: 8 | - name: "port-8080" 9 | port: 8080 10 | targetPort: 8080 11 | selector: 12 | move2kube.konveyor.io/service: "{{ .Release.Name }}-move2kubeapi" 13 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/move2kube-api-stateful-set.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: "{{ .Release.Name }}-move2kubeapi" 5 | labels: 6 | move2kube.konveyor.io/service: "{{ .Release.Name }}-move2kubeapi" 7 | spec: 8 | replicas: {{ .Values.deployment.api.replicas }} 9 | selector: 10 | matchLabels: 11 | move2kube.konveyor.io/service: "{{ .Release.Name }}-move2kubeapi" 12 | serviceName: "{{ .Release.Name }}-move2kubeapiheadless" 13 | template: 14 | metadata: 15 | name: "{{ .Release.Name }}-move2kubeapi" 16 | labels: 17 | move2kube.konveyor.io/service: "{{ .Release.Name }}-move2kubeapi" 18 | spec: 19 | {{- if and (.Values.deployment.api.initContainer) (.Values.persistentVolumeClaim.enable) }} 20 | initContainers: 21 | - name: change-perms 22 | image: quay.io/konveyor/busybox 23 | command: ["/bin/sh", "-c"] 24 | args: 25 | - 'chown -R "$(id -u)":root /move2kube-api/data && chmod -R 770 /move2kube-api/data' 26 | volumeMounts: 27 | - name: common-volume 28 | mountPath: /move2kube-api/data 29 | subPath: move2kube-api/data 30 | {{- end }} 31 | containers: 32 | - name: move2kubeapi 33 | image: "{{ .Values.deployment.api.imageName }}:{{ .Values.deployment.api.imageTag }}" 34 | command: 35 | - move2kube-api 36 | args: 37 | - '--host=$(MY_POD).$(MY_SERVICE)' 38 | - '--static-files-dir=build' 39 | {{- if .Values.secret.api.enable }} 40 | - '--config=config.yaml' 41 | {{- end }} 42 | {{- range .Values.deployment.api.extraArgs }} 43 | - '{{ . }}' 44 | {{- end }} 45 | imagePullPolicy: Always 46 | ports: 47 | - containerPort: 8080 48 | protocol: TCP 49 | env: 50 | - name: MY_POD 51 | valueFrom: 52 | fieldRef: 53 | fieldPath: metadata.name 54 | - name: MY_SERVICE 55 | value: "{{ .Release.Name }}-move2kubeapiheadless" 56 | {{- if .Values.deployment.api.privilegedPods }} 57 | securityContext: 58 | privileged: true 59 | {{- end }} 60 | volumeMounts: 61 | {{- if .Values.secret.api.enable }} 62 | - name: api-config 63 | mountPath: /move2kube-api/config.yaml 64 | subPath: config.yaml 65 | readOnly: true 66 | {{- end }} 67 | - name: common-volume 68 | mountPath: /move2kube-api/data 69 | {{- if .Values.persistentVolumeClaim.enable }} 70 | subPath: move2kube-api/data 71 | {{- end }} 72 | {{- if .Values.deployment.api.privilegedPods }} 73 | - mountPath: /var/lib/containers 74 | name: container-storage 75 | {{- end }} 76 | resources: 77 | limits: 78 | memory: "{{ .Values.deployment.api.memory }}" 79 | cpu: "{{ .Values.deployment.api.cpu }}" 80 | restartPolicy: Always 81 | volumes: 82 | {{- if .Values.secret.api.enable }} 83 | - name: api-config 84 | secret: 85 | secretName: '{{ .Values.secret.api.name | default (print .Release.Name "-move2kubeapi") }}' 86 | {{- end }} 87 | - name: common-volume 88 | {{- if .Values.persistentVolumeClaim.enable }} 89 | persistentVolumeClaim: 90 | claimName: '{{ .Values.persistentVolumeClaim.name | default (print .Release.Name "-move2kubeapi") }}' 91 | {{- else }} 92 | emptyDir: {} 93 | {{- end }} 94 | {{- if .Values.deployment.api.privilegedPods }} 95 | - name: container-storage 96 | emptyDir: {} 97 | {{- end }} 98 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/persistent-volume-claim.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.persistentVolumeClaim.enable) (.Values.persistentVolumeClaim.createNew) }} 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: '{{ .Values.persistentVolumeClaim.name | default (print .Release.Name "-move2kubeapi") }}' 6 | labels: 7 | move2kube.konveyor.io/service: "{{ .Release.Name }}-move2kubeapi" 8 | spec: 9 | storageClassName: "{{ .Values.persistentVolumeClaim.storageClassName }}" 10 | accessModes: 11 | {{- if or (gt (int .Values.deployment.api.replicas) 1) (.Values.deployment.database.enable) (.Values.persistentVolumeClaim.readWriteMany) }} 12 | - ReadWriteMany 13 | {{- else }} 14 | - ReadWriteOnce 15 | {{- end }} 16 | volumeMode: Filesystem 17 | resources: 18 | requests: 19 | storage: "{{ .Values.persistentVolumeClaim.storageSize }}" 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/security-context-constraints.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.securityContextContraints.enable) (.Capabilities.APIVersions.Has "security.openshift.io/v1/SecurityContextConstraints") }} 2 | kind: SecurityContextConstraints 3 | apiVersion: security.openshift.io/v1 4 | metadata: 5 | name: "{{ .Release.Name }}-move2kube" 6 | annotations: 7 | helm.sh/hook: pre-install 8 | helm.sh/hook-weight: "-1" 9 | users: 10 | - "system:serviceaccount:{{ .Release.Namespace }}:{{ .Values.serviceAccount.name | default (print .Release.Name "-move2kube") }}" 11 | fsGroup: 12 | type: RunAsAny 13 | priority: 100 14 | allowHostDirVolumePlugin: false 15 | allowHostIPC: false 16 | allowHostNetwork: false 17 | allowHostPID: false 18 | allowHostPorts: false 19 | allowPrivilegeEscalation: {{ .Values.deployment.api.privilegedPods }} 20 | allowPrivilegedContainer: {{ .Values.deployment.api.privilegedPods }} 21 | runAsUser: 22 | type: RunAsAny 23 | readOnlyRootFilesystem: false 24 | requiredDropCapabilities: 25 | - KILL 26 | - MKNOD 27 | - SETUID 28 | - SETGID 29 | seLinuxContext: 30 | type: MustRunAs 31 | supplementalGroups: 32 | type: RunAsAny 33 | volumes: 34 | - configMap 35 | - downwardAPI 36 | - emptyDir 37 | - persistentVolumeClaim 38 | - projected 39 | - secret 40 | allowedCapabilities: [] 41 | defaultAddCapabilities: [] 42 | {{- end }} 43 | -------------------------------------------------------------------------------- /helm-charts/move2kube/templates/service-account.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Values.serviceAccount.enable) (.Values.serviceAccount.createNew) }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: '{{ .Values.serviceAccount.name | default (print .Release.Name "-move2kube") }}' 6 | {{- end }} 7 | -------------------------------------------------------------------------------- /helm-charts/move2kube/values.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-06/schema#", 3 | "$ref": "#/definitions/ValuesYAML", 4 | "definitions": { 5 | "ValuesYAML": { 6 | "type": "object", 7 | "additionalProperties": false, 8 | "properties": { 9 | "deployment": { 10 | "$ref": "#/definitions/Deployment" 11 | }, 12 | "ingress": { 13 | "$ref": "#/definitions/Ingress" 14 | }, 15 | "persistentVolumeClaim": { 16 | "$ref": "#/definitions/PersistentVolumeClaim" 17 | }, 18 | "secret": { 19 | "$ref": "#/definitions/Secret" 20 | }, 21 | "securityContextContraints": { 22 | "$ref": "#/definitions/SecurityContextContraints" 23 | }, 24 | "serviceAccount": { 25 | "$ref": "#/definitions/ServiceAccount" 26 | } 27 | }, 28 | "required": [ 29 | "deployment", 30 | "ingress", 31 | "persistentVolumeClaim", 32 | "secret", 33 | "securityContextContraints", 34 | "serviceAccount" 35 | ], 36 | "title": "ValuesYAML" 37 | }, 38 | "Deployment": { 39 | "type": "object", 40 | "additionalProperties": false, 41 | "properties": { 42 | "api": { 43 | "$ref": "#/definitions/API" 44 | }, 45 | "authServer": { 46 | "$ref": "#/definitions/AuthServer" 47 | }, 48 | "database": { 49 | "$ref": "#/definitions/AuthServer" 50 | } 51 | }, 52 | "required": [ 53 | "api", 54 | "authServer", 55 | "database" 56 | ], 57 | "title": "Deployment" 58 | }, 59 | "API": { 60 | "type": "object", 61 | "additionalProperties": false, 62 | "properties": { 63 | "replicas": { 64 | "type": "integer" 65 | }, 66 | "privilegedPods": { 67 | "type": "boolean" 68 | }, 69 | "imageName": { 70 | "type": "string" 71 | }, 72 | "imageTag": { 73 | "type": "string" 74 | }, 75 | "extraArgs": { 76 | "type": "array", 77 | "items": { 78 | "type": "string" 79 | } 80 | }, 81 | "memory": { 82 | "type": "string" 83 | }, 84 | "cpu": { 85 | "type": "string" 86 | }, 87 | "initContainer": { 88 | "type": "boolean" 89 | } 90 | }, 91 | "required": [ 92 | "cpu", 93 | "imageTag", 94 | "memory", 95 | "privilegedPods", 96 | "replicas" 97 | ], 98 | "title": "API" 99 | }, 100 | "AuthServer": { 101 | "type": "object", 102 | "additionalProperties": false, 103 | "properties": { 104 | "enable": { 105 | "type": "boolean" 106 | }, 107 | "replicas": { 108 | "type": "integer" 109 | }, 110 | "memory": { 111 | "type": "string" 112 | }, 113 | "cpu": { 114 | "type": "string" 115 | }, 116 | "startEmpty": { 117 | "type": "boolean" 118 | } 119 | }, 120 | "required": [ 121 | "cpu", 122 | "enable", 123 | "memory", 124 | "replicas" 125 | ], 126 | "title": "AuthServer" 127 | }, 128 | "Ingress": { 129 | "type": "object", 130 | "additionalProperties": false, 131 | "properties": { 132 | "enable": { 133 | "type": "boolean" 134 | }, 135 | "preferRoute": { 136 | "type": "boolean" 137 | }, 138 | "host": { 139 | "type": "string" 140 | }, 141 | "tlsSecretName": { 142 | "type": "string" 143 | } 144 | }, 145 | "required": [ 146 | "enable", 147 | "host", 148 | "preferRoute", 149 | "tlsSecretName" 150 | ], 151 | "title": "Ingress" 152 | }, 153 | "PersistentVolumeClaim": { 154 | "type": "object", 155 | "additionalProperties": false, 156 | "properties": { 157 | "enable": { 158 | "type": "boolean" 159 | }, 160 | "createNew": { 161 | "type": "boolean" 162 | }, 163 | "name": { 164 | "type": "string" 165 | }, 166 | "storageClassName": { 167 | "type": "string" 168 | }, 169 | "storageSize": { 170 | "type": "string" 171 | }, 172 | "readWriteMany": { 173 | "type": "boolean" 174 | } 175 | }, 176 | "required": [ 177 | "createNew", 178 | "enable", 179 | "name", 180 | "storageClassName", 181 | "storageSize" 182 | ], 183 | "title": "PersistentVolumeClaim" 184 | }, 185 | "Secret": { 186 | "type": "object", 187 | "additionalProperties": false, 188 | "properties": { 189 | "api": { 190 | "$ref": "#/definitions/ServiceAccount" 191 | }, 192 | "authServer": { 193 | "$ref": "#/definitions/SecretAuthServer" 194 | } 195 | }, 196 | "required": [ 197 | "api", 198 | "authServer" 199 | ], 200 | "title": "Secret" 201 | }, 202 | "ServiceAccount": { 203 | "type": "object", 204 | "additionalProperties": false, 205 | "properties": { 206 | "enable": { 207 | "type": "boolean" 208 | }, 209 | "createNew": { 210 | "type": "boolean" 211 | }, 212 | "name": { 213 | "type": "string" 214 | }, 215 | "configYAML": { 216 | "type": "string" 217 | } 218 | }, 219 | "required": [ 220 | "createNew", 221 | "enable", 222 | "name" 223 | ], 224 | "title": "ServiceAccount" 225 | }, 226 | "SecretAuthServer": { 227 | "type": "object", 228 | "additionalProperties": false, 229 | "properties": { 230 | "createNew": { 231 | "type": "boolean" 232 | }, 233 | "name": { 234 | "type": "string" 235 | }, 236 | "adminUsername": { 237 | "type": "string" 238 | }, 239 | "adminPassword": { 240 | "type": "string" 241 | }, 242 | "databaseUsername": { 243 | "type": "string" 244 | }, 245 | "databasePassword": { 246 | "type": "string" 247 | }, 248 | "realmJSON": { 249 | "type": "string" 250 | }, 251 | "standaloneHAXML": { 252 | "type": "string" 253 | } 254 | }, 255 | "required": [ 256 | "adminPassword", 257 | "adminUsername", 258 | "createNew", 259 | "databasePassword", 260 | "databaseUsername", 261 | "name", 262 | "realmJSON", 263 | "standaloneHAXML" 264 | ], 265 | "title": "SecretAuthServer" 266 | }, 267 | "SecurityContextContraints": { 268 | "type": "object", 269 | "additionalProperties": false, 270 | "properties": { 271 | "enable": { 272 | "type": "boolean" 273 | } 274 | }, 275 | "required": [ 276 | "enable" 277 | ], 278 | "title": "SecurityContextContraints" 279 | } 280 | } 281 | } -------------------------------------------------------------------------------- /helm-charts/move2kube/values.yaml: -------------------------------------------------------------------------------- 1 | deployment: 2 | api: 3 | replicas: 1 4 | privilegedPods: false # some optional features of Move2Kube require privileged pods 5 | imageName: "quay.io/konveyor/move2kube-ui" 6 | imageTag: "latest" 7 | extraArgs: ["--log-level=trace", "--max-upload-size=104857600"] 8 | memory: "1Gi" 9 | cpu: "300m" 10 | initContainer: false 11 | authServer: 12 | enable: false 13 | replicas: 1 14 | memory: "1Gi" 15 | cpu: "300m" 16 | database: 17 | enable: false 18 | replicas: 1 19 | startEmpty: true 20 | memory: "1Gi" 21 | cpu: "300m" 22 | ingress: 23 | enable: true 24 | preferRoute: false # if true we will use an Openshift Route instead of an K8s Ingress 25 | host: "mydomain.com" 26 | tlsSecretName: "" # not necessary when using Openshift Route 27 | persistentVolumeClaim: 28 | enable: false # set this to true to get persistent storage 29 | createNew: true 30 | name: "" # if createNew is false this MUST contain the name of an existing persistent volume claim 31 | storageClassName: "" 32 | storageSize: "1Gi" 33 | readWriteMany: true 34 | secret: 35 | api: 36 | enable: false 37 | createNew: true 38 | name: "" # if createNew is false this MUST contain the name of an existing secret 39 | configYAML: "" 40 | authServer: # required if deployment.authServer.enable is true 41 | createNew: true 42 | name: "" # if createNew is false this MUST contain the name of an existing secret 43 | adminUsername: "admin" # TODO: Change this before deploying 44 | adminPassword: "password" # TODO: Change this before deploying 45 | databaseUsername: "auth-server" # TODO: Change this before deploying 46 | databasePassword: "password" # TODO: Change this before deploying 47 | realmJSON: "" 48 | standaloneHAXML: "" 49 | securityContextContraints: 50 | enable: false 51 | serviceAccount: 52 | enable: false 53 | createNew: true 54 | name: "" # if createNew is false this MUST contain the name of an existing service account 55 | -------------------------------------------------------------------------------- /operator/.dockerignore: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corporation 2020 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | Dockerfile 16 | deploy 17 | .git 18 | .gitignore 19 | secrets/ 20 | -------------------------------------------------------------------------------- /operator/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright IBM Corporation 2020 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | .vscode 16 | demo_tmp 17 | sample_tmp 18 | work_dir 19 | m2k 20 | m2k_bin 21 | colleted_md 22 | .DS_Store 23 | _dist 24 | bin 25 | .m2k 26 | coverage.txt 27 | .cr-release-packages 28 | secrets/ 29 | /helm-charts/ 30 | /bundle/ 31 | -------------------------------------------------------------------------------- /operator/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM quay.io/operator-framework/helm-operator:v1.31.0 3 | 4 | ENV HOME=/opt/helm 5 | COPY watches.yaml ${HOME}/watches.yaml 6 | COPY helm-charts ${HOME}/helm-charts 7 | WORKDIR ${HOME} 8 | -------------------------------------------------------------------------------- /operator/PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: konveyor.io 6 | layout: 7 | - helm.sdk.operatorframework.io/v1 8 | plugins: 9 | manifests.sdk.operatorframework.io/v2: {} 10 | scorecard.sdk.operatorframework.io/v2: {} 11 | projectName: move2kube-operator 12 | resources: 13 | - api: 14 | crdVersion: v1 15 | namespaced: true 16 | domain: konveyor.io 17 | group: move2kube 18 | kind: Move2Kube 19 | version: v1alpha1 20 | version: "3" 21 | -------------------------------------------------------------------------------- /operator/README.md: -------------------------------------------------------------------------------- 1 | [![Docker Repository on Quay](https://quay.io/repository/konveyor/move2kube-operator/status "Docker Repository on Quay")](https://quay.io/repository/konveyor/move2kube-operator) 2 | [![License](https://img.shields.io/:license-apache-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 3 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/konveyor/move2kube-ui/pulls) 4 | [](https://kubernetes.slack.com/archives/CR85S82A2) 5 | 6 | # Move2Kube-Operator 7 | 8 | Operator to orchestrate [Move2Kube UI](https://github.com/konveyor/move2kube-ui) and [API](https://github.com/konveyor/move2kube-api). 9 | 10 | ## Prerequisites 11 | 12 | - Kubectl 13 | - Helm 3 14 | - A Kubernetes cluster 15 | 16 | ## Usage 17 | 18 | > A new Golang based operator is in the works 19 | 20 | The instructions below are for installing the current Helm based operator: 21 | 22 | 1. Deploy the operator either using the OLM tool or directly: 23 | https://sdk.operatorframework.io/docs/building-operators/helm/quickstart/#olm-deployment 24 | https://sdk.operatorframework.io/docs/building-operators/helm/quickstart/#direct-deployment 25 | 26 | 1. Then we can apply the CR. Make sure to edit the CR yaml with the proper values before applying. 27 | ```console 28 | $ kubectl apply -f config/samples/demo_v1alpha1_nginx.yaml 29 | ``` 30 | For more details check https://github.com/konveyor/move2kube-ui/blob/main/helm-charts/move2kube/README.md 31 | 32 | ## Discussion 33 | 34 | * For any questions reach out to us on any of the communication channels given on our website https://move2kube.konveyor.io/. 35 | -------------------------------------------------------------------------------- /operator/bundle.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | # Core bundle labels. 4 | LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 5 | LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ 6 | LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ 7 | LABEL operators.operatorframework.io.bundle.package.v1=move2kube-operator 8 | LABEL operators.operatorframework.io.bundle.channels.v1=alpha 9 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.31.0 10 | LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 11 | LABEL operators.operatorframework.io.metrics.project_layout=helm.sdk.operatorframework.io/v1 12 | 13 | # Labels for testing. 14 | LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 15 | LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ 16 | 17 | # Copy files to locations specified by labels. 18 | COPY bundle/manifests /manifests/ 19 | COPY bundle/metadata /metadata/ 20 | COPY bundle/tests/scorecard /tests/scorecard/ 21 | -------------------------------------------------------------------------------- /operator/config/crd/bases/konveyor.io_move2kubes.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: move2kubes.konveyor.io 6 | spec: 7 | group: konveyor.io 8 | names: 9 | kind: Move2Kube 10 | listKind: Move2KubeList 11 | plural: move2kubes 12 | singular: move2kube 13 | scope: Namespaced 14 | versions: 15 | - name: v1alpha1 16 | schema: 17 | openAPIV3Schema: 18 | description: Move2Kube is the Schema for the move2kubes API 19 | properties: 20 | apiVersion: 21 | description: 'APIVersion defines the versioned schema of this representation 22 | of an object. Servers should convert recognized schemas to the latest 23 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 24 | type: string 25 | kind: 26 | description: 'Kind is a string value representing the REST resource this 27 | object represents. Servers may infer this from the endpoint the client 28 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 29 | type: string 30 | metadata: 31 | type: object 32 | spec: 33 | description: Spec defines the desired state of Move2Kube 34 | type: object 35 | x-kubernetes-preserve-unknown-fields: true 36 | status: 37 | description: Status defines the observed state of Move2Kube 38 | type: object 39 | x-kubernetes-preserve-unknown-fields: true 40 | type: object 41 | served: true 42 | storage: true 43 | subresources: 44 | status: {} 45 | -------------------------------------------------------------------------------- /operator/config/crd/bases/move2kube.konveyor.io_move2kubes.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: move2kubes.move2kube.konveyor.io 6 | spec: 7 | group: move2kube.konveyor.io 8 | names: 9 | kind: Move2Kube 10 | listKind: Move2KubeList 11 | plural: move2kubes 12 | singular: move2kube 13 | scope: Namespaced 14 | versions: 15 | - name: v1alpha1 16 | schema: 17 | openAPIV3Schema: 18 | description: Move2Kube is the Schema for the move2kubes API 19 | properties: 20 | apiVersion: 21 | description: 'APIVersion defines the versioned schema of this representation 22 | of an object. Servers should convert recognized schemas to the latest 23 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 24 | type: string 25 | kind: 26 | description: 'Kind is a string value representing the REST resource this 27 | object represents. Servers may infer this from the endpoint the client 28 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 29 | type: string 30 | metadata: 31 | type: object 32 | spec: 33 | description: Spec defines the desired state of Move2Kube 34 | type: object 35 | x-kubernetes-preserve-unknown-fields: true 36 | status: 37 | description: Status defines the observed state of Move2Kube 38 | type: object 39 | x-kubernetes-preserve-unknown-fields: true 40 | type: object 41 | served: true 42 | storage: true 43 | subresources: 44 | status: {} 45 | -------------------------------------------------------------------------------- /operator/config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/move2kube.konveyor.io_move2kubes.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | -------------------------------------------------------------------------------- /operator/config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: move2kube-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: move2kube-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #labels: 13 | #- includeSelectors: true 14 | # pairs: 15 | # someName: someValue 16 | 17 | resources: 18 | - ../crd 19 | - ../rbac 20 | - ../manager 21 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 22 | #- ../prometheus 23 | 24 | patchesStrategicMerge: 25 | # Protect the /metrics endpoint by putting it behind auth. 26 | # If you want your controller-manager to expose the /metrics 27 | # endpoint w/o any authn/z, please comment the following line. 28 | - manager_auth_proxy_patch.yaml 29 | 30 | 31 | -------------------------------------------------------------------------------- /operator/config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | affinity: 12 | nodeAffinity: 13 | requiredDuringSchedulingIgnoredDuringExecution: 14 | nodeSelectorTerms: 15 | - matchExpressions: 16 | - key: kubernetes.io/arch 17 | operator: In 18 | values: 19 | - amd64 20 | - arm64 21 | - ppc64le 22 | - s390x 23 | - key: kubernetes.io/os 24 | operator: In 25 | values: 26 | - linux 27 | containers: 28 | - name: kube-rbac-proxy 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | capabilities: 32 | drop: 33 | - "ALL" 34 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1 35 | args: 36 | - "--secure-listen-address=0.0.0.0:8443" 37 | - "--upstream=http://127.0.0.1:8080/" 38 | - "--logtostderr=true" 39 | - "--v=0" 40 | ports: 41 | - containerPort: 8443 42 | protocol: TCP 43 | name: https 44 | resources: 45 | limits: 46 | cpu: 500m 47 | memory: 128Mi 48 | requests: 49 | cpu: 5m 50 | memory: 64Mi 51 | - name: manager 52 | args: 53 | - "--health-probe-bind-address=:8081" 54 | - "--metrics-bind-address=127.0.0.1:8080" 55 | - "--leader-elect" 56 | - "--leader-election-id=move2kube-operator" 57 | -------------------------------------------------------------------------------- /operator/config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | -------------------------------------------------------------------------------- /operator/config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: quay.io/konveyor/move2kube-operator 8 | newTag: v0.3.10-rc.0 9 | -------------------------------------------------------------------------------- /operator/config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: namespace 7 | app.kubernetes.io/instance: system 8 | app.kubernetes.io/component: manager 9 | app.kubernetes.io/created-by: move2kube-operator 10 | app.kubernetes.io/part-of: move2kube-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: controller-manager 18 | namespace: system 19 | labels: 20 | control-plane: controller-manager 21 | app.kubernetes.io/name: deployment 22 | app.kubernetes.io/instance: controller-manager 23 | app.kubernetes.io/component: manager 24 | app.kubernetes.io/created-by: move2kube-operator 25 | app.kubernetes.io/part-of: move2kube-operator 26 | app.kubernetes.io/managed-by: kustomize 27 | spec: 28 | selector: 29 | matchLabels: 30 | control-plane: controller-manager 31 | replicas: 1 32 | template: 33 | metadata: 34 | annotations: 35 | kubectl.kubernetes.io/default-container: manager 36 | labels: 37 | control-plane: controller-manager 38 | spec: 39 | # TODO(user): Uncomment the following code to configure the nodeAffinity expression 40 | # according to the platforms which are supported by your solution. 41 | # It is considered best practice to support multiple architectures. You can 42 | # build your manager image using the makefile target docker-buildx. 43 | # affinity: 44 | # nodeAffinity: 45 | # requiredDuringSchedulingIgnoredDuringExecution: 46 | # nodeSelectorTerms: 47 | # - matchExpressions: 48 | # - key: kubernetes.io/arch 49 | # operator: In 50 | # values: 51 | # - amd64 52 | # - arm64 53 | # - ppc64le 54 | # - s390x 55 | # - key: kubernetes.io/os 56 | # operator: In 57 | # values: 58 | # - linux 59 | securityContext: 60 | runAsNonRoot: true 61 | # TODO(user): For common cases that do not require escalating privileges 62 | # it is recommended to ensure that all your Pods/Containers are restrictive. 63 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 64 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 65 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 66 | # seccompProfile: 67 | # type: RuntimeDefault 68 | containers: 69 | - args: 70 | - --leader-elect 71 | - --leader-election-id=move2kube-operator 72 | image: controller:latest 73 | name: manager 74 | securityContext: 75 | allowPrivilegeEscalation: false 76 | capabilities: 77 | drop: 78 | - "ALL" 79 | livenessProbe: 80 | httpGet: 81 | path: /healthz 82 | port: 8081 83 | initialDelaySeconds: 15 84 | periodSeconds: 20 85 | readinessProbe: 86 | httpGet: 87 | path: /readyz 88 | port: 8081 89 | initialDelaySeconds: 5 90 | periodSeconds: 10 91 | # TODO(user): Configure the resources accordingly based on the project requirements. 92 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 93 | resources: 94 | limits: 95 | cpu: 1 96 | memory: 1Gi 97 | requests: 98 | cpu: 100m 99 | memory: 128Mi 100 | serviceAccountName: controller-manager 101 | terminationGracePeriodSeconds: 10 102 | -------------------------------------------------------------------------------- /operator/config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/move2kube-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | -------------------------------------------------------------------------------- /operator/config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /operator/config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | app.kubernetes.io/name: servicemonitor 9 | app.kubernetes.io/instance: controller-manager-metrics-monitor 10 | app.kubernetes.io/component: metrics 11 | app.kubernetes.io/created-by: move2kube-operator 12 | app.kubernetes.io/part-of: move2kube-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controller-manager-metrics-monitor 15 | namespace: system 16 | spec: 17 | endpoints: 18 | - path: /metrics 19 | port: https 20 | scheme: https 21 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 22 | tlsConfig: 23 | insecureSkipVerify: true 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | -------------------------------------------------------------------------------- /operator/config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: move2kube-operator 9 | app.kubernetes.io/part-of: move2kube-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /operator/config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: move2kube-operator 9 | app.kubernetes.io/part-of: move2kube-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /operator/config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: move2kube-operator 9 | app.kubernetes.io/part-of: move2kube-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /operator/config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-manager-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: move2kube-operator 10 | app.kubernetes.io/part-of: move2kube-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-manager-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-manager 22 | -------------------------------------------------------------------------------- /operator/config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /operator/config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: move2kube-operator 10 | app.kubernetes.io/part-of: move2kube-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /operator/config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: move2kube-operator 9 | app.kubernetes.io/part-of: move2kube-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /operator/config/rbac/move2kube_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit move2kubes. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: move2kube-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: move2kube-operator 10 | app.kubernetes.io/part-of: move2kube-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: move2kube-editor-role 13 | rules: 14 | - apiGroups: 15 | - move2kube.konveyor.io 16 | resources: 17 | - move2kubes 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - move2kube.konveyor.io 28 | resources: 29 | - move2kubes/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /operator/config/rbac/move2kube_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view move2kubes. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: move2kube-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: move2kube-operator 10 | app.kubernetes.io/part-of: move2kube-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: move2kube-viewer-role 13 | rules: 14 | - apiGroups: 15 | - move2kube.konveyor.io 16 | resources: 17 | - move2kubes 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - move2kube.konveyor.io 24 | resources: 25 | - move2kubes/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /operator/config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: manager-role 5 | rules: 6 | ## 7 | ## Base operator rules 8 | ## 9 | # We need to get namespaces so the operator can read namespaces to ensure they exist 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - namespaces 14 | verbs: 15 | - get 16 | # We need to manage Helm release secrets 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - secrets 21 | verbs: 22 | - "*" 23 | # We need to create events on CRs about things happening during reconciliation 24 | - apiGroups: 25 | - "" 26 | resources: 27 | - events 28 | verbs: 29 | - create 30 | 31 | ## 32 | ## Rules for move2kube.konveyor.io/v1alpha1, Kind: Move2Kube 33 | ## 34 | - apiGroups: 35 | - move2kube.konveyor.io 36 | resources: 37 | - move2kubes 38 | - move2kubes/status 39 | - move2kubes/finalizers 40 | verbs: 41 | - create 42 | - delete 43 | - get 44 | - list 45 | - patch 46 | - update 47 | - watch 48 | - verbs: 49 | - "*" 50 | apiGroups: 51 | - "networking.k8s.io" 52 | resources: 53 | - "ingresses" 54 | - verbs: 55 | - "*" 56 | apiGroups: 57 | - "" 58 | resources: 59 | - "persistentvolumeclaims" 60 | - "serviceaccounts" 61 | - "services" 62 | - "configmaps" 63 | - verbs: 64 | - "*" 65 | apiGroups: 66 | - "apps" 67 | resources: 68 | - "deployments" 69 | - "statefulsets" 70 | - verbs: 71 | - "*" 72 | apiGroups: 73 | - "route.openshift.io" 74 | resources: 75 | - "routes" 76 | - verbs: 77 | - "*" 78 | apiGroups: 79 | - "security.openshift.io" 80 | resources: 81 | - "securitycontextconstraints" 82 | 83 | #+kubebuilder:scaffold:rules 84 | -------------------------------------------------------------------------------- /operator/config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: manager-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: move2kube-operator 9 | app.kubernetes.io/part-of: move2kube-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: manager-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: manager-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /operator/config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kubernetes.io/instance: controller-manager-sa 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: move2kube-operator 9 | app.kubernetes.io/part-of: move2kube-operator 10 | app.kubernetes.io/managed-by: kustomize 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /operator/config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - move2kube_v1alpha1_move2kube.yaml 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /operator/config/samples/move2kube_v1alpha1_move2kube.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: move2kube.konveyor.io/v1alpha1 2 | kind: Move2Kube 3 | metadata: 4 | name: move2kube-sample 5 | spec: {} 6 | -------------------------------------------------------------------------------- /operator/config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /operator/config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /operator/config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.31.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /operator/config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.31.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.31.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.31.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.31.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.31.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /operator/watches.yaml: -------------------------------------------------------------------------------- 1 | # Use the 'create api' subcommand to add watches to this file. 2 | - group: move2kube.konveyor.io 3 | version: v1alpha1 4 | kind: Move2Kube 5 | chart: helm-charts/move2kube 6 | #+kubebuilder:scaffold:watch 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "move2kube-ui", 3 | "version": "0.1.0", 4 | "description": "A UI for Move2Kube.", 5 | "license": "Apache-2.0", 6 | "repository": "https://github.com/konveyor/move2kube-ui.git", 7 | "private": true, 8 | "packageManager": "pnpm@8.5.1", 9 | "engines": { 10 | "node": ">=16.9.0" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.10.5", 14 | "@emotion/styled": "^11.10.5", 15 | "@mui/material": "^5.11.3", 16 | "@mui/x-data-grid": "^5.17.18", 17 | "@patternfly/react-core": "^4.267.6", 18 | "@patternfly/react-icons": "^4.93.3", 19 | "@reduxjs/toolkit": "^1.9.1", 20 | "http-proxy-middleware": "^2.0.6", 21 | "prismjs": "^1.29.0", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-redux": "^8.0.5", 25 | "react-router-dom": "^6.6.1", 26 | "react-scripts": "5.0.1", 27 | "react-simple-code-editor": "^0.13.1", 28 | "reactflow": "^11.4.2", 29 | "typescript": "^4.9.4", 30 | "uuid": "^9.0.0" 31 | }, 32 | "scripts": { 33 | "start": "react-scripts start", 34 | "start-api": "move2kube-api --static-files-dir build/", 35 | "build": "react-scripts build", 36 | "test": "react-scripts test", 37 | "eject": "react-scripts eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": [ 41 | "react-app", 42 | "react-app/jest" 43 | ] 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "@types/node": "^18.11.18", 59 | "@types/prismjs": "^1.26.0", 60 | "@types/react": "^18.0.26", 61 | "@types/react-dom": "^18.0.10", 62 | "@types/uuid": "^9.0.1" 63 | } 64 | } -------------------------------------------------------------------------------- /podman-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | move2kubeui: 4 | build: 5 | context: ./ 6 | dockerfile: Dockerfile 7 | image: quay.io/konveyor/move2kube-ui:latest 8 | ports: 9 | - "8080:8080" 10 | volumes: 11 | - "./data:/move2kube-api/data:z" -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konveyor/move2kube-ui/4646f33c19cb3393344660f949e5bd87df892d99/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Move2Kube 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /releasenotes-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title_prefix: "[WIP] Move2Kube UI ", 3 | // valid PR types: ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert'] 4 | sections: [ 5 | { title: "🚀 Features", labels: ["enhancement", "feat", "perf"] }, 6 | { title: "🐛 Bug Fixes", labels: ["bug", "fix", "revert"] }, 7 | { title: "🧹 Maintenance", labels: ["docs", "style", "refactor", "test", "build", "ci", "chore"] }, 8 | ], 9 | header: `For more documentation and support please visit https://move2kube.konveyor.io/ 10 | # Changelog`, 11 | line_template: x => `- ${x.title} [#${x.number}](${x.html_url})`, 12 | }; 13 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Workspaces } from './features/workspaces/Workspaces'; 18 | import { Projects } from './features/projects/Projects'; 19 | import { Project } from './features/projects/Project'; 20 | import { Support } from './features/support/Support'; 21 | import { BreadCrumbs } from './features/breadcrumbs/BreadCrumbs'; 22 | import { NotFound } from './features/notfound/NotFound'; 23 | import { Routes, Route, NavLink, Link } from 'react-router-dom'; 24 | import { 25 | Page, PageSidebar, PageToggleButton, Masthead, 26 | MastheadToggle, MastheadMain, MastheadBrand, MastheadContent, 27 | Toolbar, ToolbarContent, ToolbarItem, Nav, NavList, NavItem, Alert, 28 | Spinner, Dropdown, DropdownItem, DropdownToggle, Button, AlertGroup, AlertActionCloseButton, 29 | } from '@patternfly/react-core'; 30 | import { BarsIcon } from '@patternfly/react-icons'; 31 | import { FunctionComponent, useState } from 'react'; 32 | import { Login } from './features/login/Login'; 33 | import { useGetUserProfileQuery, useLogoutMutation } from './features/login/loginApi'; 34 | import { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query'; 35 | import { extractErrMsg } from './features/common/utils'; 36 | import { deleteToast, selectToasts } from './features/toasts/toastsSlice'; 37 | import { useAppDispatch, useAppSelector } from './app/hooks'; 38 | import { TOAST_TIMEOUT_MILLISECONDS } from './features/common/constants'; 39 | 40 | export const App: FunctionComponent = () => { 41 | const { data: userProfile, isLoading: isLoadingUserProfile, error: getUserProfileErr } = useGetUserProfileQuery(); 42 | const [logout, { isLoading: isLoggingOut }] = useLogoutMutation(); 43 | const toasts = useAppSelector(selectToasts); 44 | const dispatch = useAppDispatch(); 45 | const [isLogoutOpen, setIsLogoutOpen] = useState(false); 46 | const isAuthDisabled = (getUserProfileErr as FetchBaseQueryError)?.status === 404; 47 | const isLoggedOut = (getUserProfileErr as FetchBaseQueryError)?.status === 401; 48 | 49 | const header = ( 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Move2Kube}> 58 | 59 | 60 | 61 | 62 | 63 | 64 | {isAuthDisabled ? ( 65 | null 66 | ) : isLoggedOut ? ( 67 | Login 68 | ) : getUserProfileErr ? ( 69 | 70 | ) : isLoadingUserProfile ? ( 71 | 72 | ) : userProfile ? ( 73 | setIsLogoutOpen(!isLogoutOpen)}> 77 | {userProfile.preferred_username || userProfile.email} 78 | 79 | } 80 | dropdownItems={[ 81 | 82 | 83 | 84 | ]} /> 85 | ) : ( 86 | Login 87 | )} 88 | 89 | 90 | 91 | 92 | 93 | ); 94 | const navi = ( 95 | 101 | ); 102 | const sidebar = (); 103 | return ( 104 | 105 | 106 | 107 | } /> 108 | } /> 109 | } /> 110 | } /> 111 | } /> 112 | } /> 113 | } /> 114 | } /> 115 | 116 | 117 | {toasts.map((toast) => ( 118 | dispatch(deleteToast(toast.id))} 124 | actionClose={ dispatch(deleteToast(toast.id))} />} 125 | /> 126 | ))} 127 | 128 | 129 | ); 130 | }; 131 | -------------------------------------------------------------------------------- /src/app/hooks.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 18 | import type { RootState, AppDispatch } from './store'; 19 | 20 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 21 | export const useAppDispatch = () => useDispatch(); 22 | export const useAppSelector: TypedUseSelectorHook = useSelector; 23 | -------------------------------------------------------------------------------- /src/app/store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; 18 | import { supportApi } from '../features/support/supportApi'; 19 | import { workspacesApi } from '../features/workspaces/workspacesApi'; 20 | import { projectsApi } from '../features/projects/projectsApi'; 21 | import { projectInputsApi } from '../features/inputs/inputsApi'; 22 | import { projectOutputsApi } from '../features/outputs/outputsApi'; 23 | import { planApi } from '../features/plan/planApi'; 24 | import { loginApi } from '../features/login/loginApi'; 25 | import { setupListeners } from '@reduxjs/toolkit/query'; 26 | import inputsReducer from '../features/inputs/inputsSlice'; 27 | import outputsReducer from '../features/outputs/outputsSlice'; 28 | import toastsReducer from '../features/toasts/toastsSlice'; 29 | import planReducer from '../features/plan/planSlice'; 30 | 31 | export const store = configureStore({ 32 | reducer: { 33 | [supportApi.reducerPath]: supportApi.reducer, 34 | [workspacesApi.reducerPath]: workspacesApi.reducer, 35 | [projectsApi.reducerPath]: projectsApi.reducer, 36 | [projectInputsApi.reducerPath]: projectInputsApi.reducer, 37 | [projectOutputsApi.reducerPath]: projectOutputsApi.reducer, 38 | [planApi.reducerPath]: planApi.reducer, 39 | [loginApi.reducerPath]: loginApi.reducer, 40 | inputs: inputsReducer, 41 | outputs: outputsReducer, 42 | toasts: toastsReducer, 43 | plan: planReducer, 44 | }, 45 | middleware: (getDef) => 46 | getDef() 47 | .concat(supportApi.middleware) 48 | .concat(workspacesApi.middleware) 49 | .concat(projectsApi.middleware) 50 | .concat(projectInputsApi.middleware) 51 | .concat(projectOutputsApi.middleware) 52 | .concat(planApi.middleware) 53 | .concat(loginApi.middleware), 54 | }); 55 | 56 | setupListeners(store.dispatch); 57 | 58 | export type AppDispatch = typeof store.dispatch; 59 | export type RootState = ReturnType; 60 | export type AppThunk = ThunkAction< 61 | ReturnType, 62 | RootState, 63 | unknown, 64 | Action 65 | >; 66 | -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 10 | 11 | 14 | 21 | 24 | 27 | 31 | 35 | 39 | 43 | 47 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/features/breadcrumbs/BreadCrumbs.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Card, CardBody, Split, SplitItem } from '@patternfly/react-core'; 18 | import { Link, useLocation } from 'react-router-dom'; 19 | import { Breadcrumbs as MUIBreadcrumbs } from '@mui/material'; 20 | import { ArrowRightIcon } from '@patternfly/react-icons'; 21 | 22 | const rx = /^\/login$/; 23 | const r0 = /^\/support$/; 24 | const r1 = /^\/workspaces/; 25 | const r2 = /^\/workspaces\/([a-z0-9-]+)\/projects/; 26 | const r3 = /^\/workspaces\/([a-z0-9-]+)\/projects\/([a-z0-9-]+)/; 27 | 28 | export const BreadCrumbs: React.FunctionComponent = () => { 29 | const location = useLocation(); 30 | const p = location.pathname; 31 | const r2Matches = p.match(r2); 32 | const r3Matches = p.match(r3); 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | {p === '/' && workspaces} 41 | {rx.test(p) && login} 42 | {r0.test(p) && support} 43 | {r1.test(p) && workspaces} 44 | {r2Matches && workspace {r2Matches[1].slice(0, 8)}...} 45 | {r3Matches && project {r3Matches[2].slice(0, 8)}...} 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/features/common/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export const API_BASE = '/api/v1'; 18 | export const QA_MAX_GET_NEXT_ATTEMPTS = 5; 19 | export const QA_MILLISECONDS_BETWEEN_GET_NEXT_ATTEMPTS = 3 * 1000; 20 | export const TOAST_TIMEOUT_MILLISECONDS = 8 * 1000; 21 | -------------------------------------------------------------------------------- /src/features/common/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export interface IMetadata { 18 | id: string; 19 | timestamp: string; 20 | name?: string; 21 | description?: string; 22 | } 23 | 24 | export interface IProjectInput extends IMetadata { 25 | type: ProjectInputType; 26 | } 27 | 28 | export enum ProjectInputType { 29 | Sources = 'sources', 30 | Customizations = 'customizations', 31 | Configs = 'configs', 32 | Reference = 'reference', 33 | } 34 | 35 | export interface IProjectOutput extends IMetadata { 36 | status: string; 37 | } 38 | -------------------------------------------------------------------------------- /src/features/common/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import {v4 as uuidv4} from 'uuid'; 18 | 19 | export const getUUID = (): string => uuidv4(); 20 | 21 | export const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(() => resolve(), ms)); 22 | 23 | export const normalizeFilename = (name: string): string => name.toLowerCase().replace(/[^a-z0-9]/g, '-'); 24 | 25 | type MyErr = { 26 | data: { 27 | error: { 28 | description: string; 29 | } 30 | } 31 | } 32 | 33 | export const extractErrMsg = (err: MyErr | unknown): string => (err as MyErr)?.data?.error?.description ?? JSON.stringify(err); 34 | -------------------------------------------------------------------------------- /src/features/inputs/inputsApi.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createApi, fetchBaseQuery, FetchBaseQueryError } from "@reduxjs/toolkit/query/react"; 18 | import { API_BASE } from "../common/constants"; 19 | import { IProjectInput, ProjectInputType } from '../common/types'; 20 | import { getUUID } from "../common/utils"; 21 | import { createStatus, deleteStatus, deleteStatusesWithErrors, setError, setProgress } from "./inputsSlice"; 22 | 23 | export const projectInputsApi = createApi({ 24 | reducerPath: 'projectInputsApi', 25 | baseQuery: fetchBaseQuery({ 26 | baseUrl: API_BASE, 27 | prepareHeaders: (headers) => { 28 | headers.set('Content-Type', 'application/json'); 29 | headers.set('Accept', 'application/json'); 30 | return headers; 31 | }, 32 | }), 33 | endpoints: (builder) => ({ 34 | createProjectInput: builder.mutation< 35 | { id: string }, 36 | { 37 | wid: string, 38 | pid?: string, 39 | projectInput: IProjectInput, 40 | file?: File, 41 | workspaceInputId?: string, 42 | } 43 | >({ 44 | queryFn: async ({ wid, pid, projectInput, file, workspaceInputId }, baseQueryApi, _extraOptions, _fetchWithBQ) => { 45 | if (projectInput.type === ProjectInputType.Reference) { 46 | if (!workspaceInputId) return { error: { status: 'CUSTOM_ERROR', error: 'input type reference requires a id to be specified' } as FetchBaseQueryError }; 47 | } else { 48 | if (!file) return { error: { status: 'CUSTOM_ERROR', error: 'The file is empty. Please upload a valid file.' } as FetchBaseQueryError }; 49 | } 50 | const url = pid ? `${API_BASE}/workspaces/${wid}/projects/${pid}/inputs` : `${API_BASE}/workspaces/${wid}/inputs`; 51 | const formdata = new FormData(); 52 | if (file) formdata.set('file', file); 53 | else if (workspaceInputId) formdata.set('id', workspaceInputId); 54 | else return { error: { status: 'CUSTOM_ERROR', error: 'must specify either a file or a workspace input id' } }; 55 | formdata.set('type', projectInput.type); 56 | if (projectInput.description) formdata.set('description', projectInput.description); 57 | const xhr = new XMLHttpRequest(); 58 | xhr.responseType = 'json'; 59 | const requestId = getUUID(); 60 | xhr.upload.addEventListener('progress', event => { 61 | console.log(`Uploaded ${event.loaded} bytes out of ${event.total}`); 62 | const percent = Math.round((event.loaded / event.total) * 100); 63 | baseQueryApi.dispatch(setProgress({ id: requestId, percent })); 64 | }); 65 | const dataPromise = new Promise<{ id: string }>((resolve, reject) => { 66 | xhr.addEventListener('abort', e => { 67 | console.log('XHR aborted event:', e); 68 | const err = 'the file upload was aborted'; 69 | baseQueryApi.dispatch(setError({ id: requestId, error: err })); 70 | reject(err); 71 | }); 72 | xhr.addEventListener('error', e => { 73 | console.log('XHR error event:', e); 74 | const err = 'the file upload failed with an error'; 75 | baseQueryApi.dispatch(setError({ id: requestId, error: err })); 76 | reject(err); 77 | }); 78 | xhr.addEventListener('load', () => { 79 | if (xhr.status < 200 || xhr.status > 299) { 80 | const reason = (xhr.response && typeof xhr.response === 'object') ? ( 81 | 'Error: ' + xhr.response.error.description 82 | ) : ( 83 | 'Please check the input type and try again.' 84 | ); 85 | const err = `failed to upload the file. Status: ${xhr.status} . ${reason}`; 86 | console.error(err); 87 | reject(err); 88 | return; 89 | } 90 | console.log(`File upload complete. Status: ${xhr.status}`); 91 | console.log('xhr.response', xhr.response); 92 | baseQueryApi.dispatch(deleteStatus(requestId)); 93 | resolve(xhr.response); 94 | }); 95 | xhr.open('POST', url); 96 | xhr.setRequestHeader('Accept', 'application/json'); 97 | baseQueryApi.dispatch(deleteStatusesWithErrors()); 98 | baseQueryApi.dispatch(createStatus({ id: requestId, name: file?.name || '' })); 99 | xhr.send(formdata); 100 | }); 101 | try { 102 | const data = await dataPromise; 103 | return { data }; 104 | } catch (e) { 105 | baseQueryApi.dispatch(setError({ id: requestId, error: `${e}` })); 106 | return { error: { status: 'FETCH_ERROR', error: `${e}` } as FetchBaseQueryError }; 107 | } 108 | }, 109 | }), 110 | deleteProjectInput: builder.mutation({ 111 | query: ({ wid, pid, inpId }) => ({ 112 | url: pid ? `/workspaces/${wid}/projects/${pid}/inputs/${inpId}` : `/workspaces/${wid}/inputs/${inpId}`, 113 | method: 'DELETE', 114 | }), 115 | }), 116 | deleteProjectInputs: builder.mutation }>({ 117 | queryFn: async ({ wid, pid, inpIds }, _queryApi, _extraOptions, fetchWithBQ) => { 118 | const results = await Promise.all(inpIds.map(inpId => fetchWithBQ({ 119 | url: pid ? `/workspaces/${wid}/projects/${pid}/inputs/${inpId}` : `/workspaces/${wid}/inputs/${inpId}`, 120 | method: 'DELETE', 121 | }))); 122 | const foundResult = results.find(result => result.error); 123 | if (foundResult) return { error: foundResult.error as FetchBaseQueryError }; 124 | return { data: undefined }; 125 | }, 126 | }), 127 | }), 128 | }); 129 | 130 | export const { useCreateProjectInputMutation, useDeleteProjectInputMutation, useDeleteProjectInputsMutation } = projectInputsApi; 131 | -------------------------------------------------------------------------------- /src/features/inputs/inputsSlice.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createSlice } from '@reduxjs/toolkit'; 18 | import { PayloadAction } from '@reduxjs/toolkit'; 19 | import { RootState } from '../../app/store'; 20 | 21 | export interface UploadStatus { 22 | id: string; 23 | name: string; 24 | percent: number; 25 | error?: string; 26 | } 27 | 28 | export interface InputsState { 29 | uploadStatuses: { [k: string]: UploadStatus }, 30 | } 31 | 32 | const initialState: InputsState = { 33 | uploadStatuses: {}, 34 | }; 35 | 36 | export const inputsSlice = createSlice({ 37 | name: 'inputs', 38 | initialState, 39 | reducers: { 40 | createStatus: (state, action: PayloadAction<{ id: string, name: string }>) => { 41 | state.uploadStatuses[action.payload.id] = { ...action.payload, percent: 0 }; 42 | }, 43 | setProgress: (state, action: PayloadAction<{ id: string, percent: number }>) => { 44 | if (action.payload.id in state.uploadStatuses) state.uploadStatuses[action.payload.id].percent = action.payload.percent; 45 | }, 46 | setError: (state, action: PayloadAction<{ id: string, error: string }>) => { 47 | if (action.payload.id in state.uploadStatuses) state.uploadStatuses[action.payload.id].error = action.payload.error; 48 | }, 49 | deleteStatus: (state, action: PayloadAction) => { 50 | delete state.uploadStatuses[action.payload]; 51 | }, 52 | deleteStatusesWithErrors: (state) => { 53 | const noErrorKeys = Object.keys(state.uploadStatuses).filter(k => !state.uploadStatuses[k].error); 54 | state.uploadStatuses = Object.fromEntries(noErrorKeys.map(k => [k, state.uploadStatuses[k]])); 55 | }, 56 | } 57 | }); 58 | 59 | export const { createStatus, setProgress, setError, deleteStatus, deleteStatusesWithErrors } = inputsSlice.actions; 60 | export const setSuccess = (id: string) => setProgress({ id, percent: 100 }); 61 | export const selectUploadStatus = (id: string) => (state: RootState): (UploadStatus | undefined) => state.inputs.uploadStatuses[id]; 62 | export const selectUploadStatuses = (state: RootState): ({ [k: string]: UploadStatus }) => state.inputs.uploadStatuses; 63 | 64 | export default inputsSlice.reducer; 65 | -------------------------------------------------------------------------------- /src/features/login/Login.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { PageSection, Title } from "@patternfly/react-core"; 18 | import { FunctionComponent } from "react"; 19 | 20 | export const Login: FunctionComponent = () => { 21 | return ( 22 | 23 | Login 24 | Click here to go to the login page. 25 | 26 | ); 27 | }; -------------------------------------------------------------------------------- /src/features/login/loginApi.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'; 18 | 19 | interface IUserInfo { 20 | email: string; 21 | preferred_username?: string; 22 | picture?: string; 23 | } 24 | 25 | export const loginApi = createApi({ 26 | reducerPath: 'loginApi', 27 | baseQuery: fetchBaseQuery({ 28 | prepareHeaders: (headers) => { 29 | headers.set('Content-Type', 'application/json'); 30 | headers.set('Accept', 'application/json'); 31 | return headers; 32 | } 33 | }), 34 | tagTypes: ['user'], 35 | endpoints: (builder) => ({ 36 | getUserProfile: builder.query({ 37 | queryFn: async () => { 38 | try { 39 | const res = await fetch('/auth/user-profile'); 40 | if (!res.ok) { 41 | return { error: { status: res.status, data: 'got an error status code' } as FetchBaseQueryError }; 42 | } 43 | const data = await res.json(); 44 | return { data }; 45 | } catch (e) { 46 | return { error: { status: 'CUSTOM_ERROR', error: 'failed to get the user profile.' } as FetchBaseQueryError }; 47 | } 48 | }, 49 | providesTags: ['user'], 50 | }), 51 | logout: builder.mutation({ 52 | query: () => ({ 53 | url: '/auth/logout', 54 | method: 'POST', 55 | }), 56 | invalidatesTags: ['user'], 57 | }), 58 | }) 59 | }); 60 | 61 | export const { useGetUserProfileQuery, useLogoutMutation } = loginApi; 62 | -------------------------------------------------------------------------------- /src/features/notfound/NotFound.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { FunctionComponent } from "react"; 18 | import { PageSection, Title } from '@patternfly/react-core'; 19 | import { ExclamationCircleIcon } from '@patternfly/react-icons'; 20 | 21 | export const NotFound: FunctionComponent = () => { 22 | return ( 23 | 24 | 25 | 404 Not Found! 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/features/outputs/graph/Graph.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Alert, Spinner } from "@patternfly/react-core"; 18 | import { FunctionComponent } from "react"; 19 | import { ReactFlow, Background, Controls, MiniMap, useNodesState, useEdgesState } from 'reactflow'; 20 | import { IGraph, useGetProjectOutputGraphQuery } from "../outputsApi"; 21 | import 'reactflow/dist/style.css'; 22 | import { extractErrMsg } from "../../common/utils"; 23 | 24 | interface IGraphProps { 25 | workspaceId: string; 26 | projectId: string; 27 | outputId: string; 28 | } 29 | 30 | interface IGraphBodyProps { 31 | graph: IGraph; 32 | } 33 | 34 | const process = (x: string) => { 35 | return x.split('\n').map((line, i) =>
{line}
); 36 | }; 37 | 38 | export const GraphBody: FunctionComponent = ({ graph }) => { 39 | const newNodes = graph.nodes.map((node) => ({ 40 | ...node, 41 | data: { 42 | ...node.data, 43 | label: ( 44 |
45 | {process(node.data.label as string)} 46 | {node.data.pathMappings && ( 47 |
48 |
pathMappings:
49 | {process(node.data.pathMappings)} 50 |
51 | )} 52 |
53 | ), 54 | }, 55 | })); 56 | 57 | const [nodes, _setNodes, onNodesChange] = useNodesState(newNodes); 58 | const [edges, _setEdges, onEdgesChange] = useEdgesState(graph.edges); 59 | 60 | return ( 61 | 68 | 69 | 70 | 71 | 72 | ); 73 | }; 74 | 75 | export const Graph: FunctionComponent = ({ workspaceId, projectId, outputId }) => { 76 | const { data: graph, isLoading, error } = useGetProjectOutputGraphQuery({ wid: workspaceId, pid: projectId, outputId }); 77 | return ( 78 | error ? ( 79 | 80 | ) : isLoading ? ( 81 | 82 | ) : graph ? ( 83 | 84 | ) : null 85 | ); 86 | }; 87 | -------------------------------------------------------------------------------- /src/features/outputs/outputsApi.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createApi, fetchBaseQuery, FetchBaseQueryError } from "@reduxjs/toolkit/query/react"; 18 | import { AppThunk } from "../../app/store"; 19 | import { API_BASE, QA_MAX_GET_NEXT_ATTEMPTS, QA_MILLISECONDS_BETWEEN_GET_NEXT_ATTEMPTS } from "../common/constants"; 20 | import { sleep } from "../common/utils"; 21 | import { addQAStep, selectCurrentStatus, setCurrentStatusId } from "./outputsSlice"; 22 | import { IQAStep, IQuestion } from "./qa/types"; 23 | import { Node, Edge } from 'reactflow'; 24 | 25 | const quesDefaults: { [id: string]: unknown } = { 26 | MultiSelect: [], 27 | Select: '', 28 | Input: '', 29 | Confirm: false, 30 | MultiLineInput: '', 31 | Password: '', 32 | }; 33 | 34 | export const moveToNextQuestion = (): AppThunk> => 35 | async (dispatch, getState) => { 36 | const state = getState(); 37 | const currentStatus = selectCurrentStatus(state); 38 | if (!currentStatus) { 39 | return { done: true }; 40 | } 41 | const wid = currentStatus.workspaceId; 42 | const pid = currentStatus.projectId; 43 | const outputId = currentStatus.outputId; 44 | if (currentStatus.steps.length > 0) { 45 | const prevQuestion: IQuestion = currentStatus.steps[currentStatus.steps.length - 1].question; 46 | const url = `${API_BASE}/workspaces/${wid}/projects/${pid}/outputs/${outputId}/problems/current/solution`; 47 | const res = await fetch(url, { 48 | method: 'POST', 49 | headers: { 50 | 'Content-Type': 'application/json', 51 | 'Accept': 'application/json', 52 | }, 53 | body: JSON.stringify({ 54 | solution: JSON.stringify(prevQuestion), 55 | }), 56 | }); 57 | if (!res.ok) { 58 | throw new Error(`got an error status code while trying to post the solution: ${res.status} ${res.statusText}`); 59 | } 60 | } 61 | const url = `${API_BASE}/workspaces/${wid}/projects/${pid}/outputs/${outputId}/problems/current`; 62 | let res: Response | null = null; 63 | let attempt = 0; 64 | while (attempt < QA_MAX_GET_NEXT_ATTEMPTS && (!res || !res.ok)) { 65 | attempt++; 66 | res = await fetch(url, { 67 | headers: { 68 | 'Content-Type': 'application/json', 69 | 'Accept': 'application/json', 70 | } 71 | }); 72 | if (res.ok) { 73 | break; 74 | } 75 | console.error(`got an error status code: ${res.status} ${res.statusText} trying again after a few seconds...`); 76 | await sleep(QA_MILLISECONDS_BETWEEN_GET_NEXT_ATTEMPTS); 77 | } 78 | if (!res) { 79 | throw new Error('did not even try to fetch'); 80 | } 81 | if (!res.ok) { 82 | throw new Error(`got an error status code: ${res.status} ${res.statusText}`); 83 | } 84 | if (res.status === 204) { 85 | console.log('finished all the questions for this transformation'); 86 | dispatch(setCurrentStatusId('')); 87 | return { done: true }; 88 | } 89 | const data: { question: string } = await res.json(); 90 | const question: IQuestion = JSON.parse(data.question); 91 | question.answer = 'default' in question ? question.default : quesDefaults[question.type]; 92 | const step: IQAStep = { question }; 93 | dispatch(addQAStep({ id: outputId, step })); 94 | return { done: false }; 95 | }; 96 | 97 | // ------------------------------------------------------------ 98 | 99 | export interface IGraph { 100 | nodes: Array; 101 | edges: Array; 102 | } 103 | 104 | export const projectOutputsApi = createApi({ 105 | reducerPath: 'projectOutputsApi', 106 | baseQuery: fetchBaseQuery({ 107 | baseUrl: API_BASE, 108 | prepareHeaders: (headers) => { 109 | headers.set('Content-Type', 'application/json'); 110 | headers.set('Accept', 'application/json'); 111 | return headers; 112 | }, 113 | }), 114 | tagTypes: ['output-graph'], 115 | endpoints: (builder) => ({ 116 | getProjectOutputGraph: builder.query({ 117 | query: ({ wid, pid, outputId }) => `/workspaces/${wid}/projects/${pid}/outputs/${outputId}/graph`, 118 | providesTags: (res, err, arg) => [{ type: 'output-graph', id: arg.outputId }], 119 | }), 120 | startTransforming: builder.mutation<{ id: string }, { wid: string, pid: string }>({ 121 | query: ({ wid, pid }) => ({ 122 | url: `/workspaces/${wid}/projects/${pid}/outputs`, 123 | method: 'POST', 124 | }), 125 | }), 126 | deleteProjectOutput: builder.mutation({ 127 | query: ({ wid, pid, outputId }) => ({ 128 | url: `/workspaces/${wid}/projects/${pid}/outputs/${outputId}`, 129 | method: 'DELETE', 130 | }), 131 | invalidatesTags: (res, err, arg) => [{ type: 'output-graph', id: arg.outputId }], 132 | }), 133 | deleteProjectOutputs: builder.mutation }>({ 134 | queryFn: async ({ wid, pid, outputIds }, _queryApi, _extraOptions, fetchWithBQ) => { 135 | const results = await Promise.all(outputIds.map(outputId => fetchWithBQ({ 136 | url: `/workspaces/${wid}/projects/${pid}/outputs/${outputId}`, 137 | method: 'DELETE', 138 | }))); 139 | const foundResult = results.find(result => result.error); 140 | if (foundResult) return { error: foundResult.error as FetchBaseQueryError }; 141 | return { data: undefined }; 142 | }, 143 | invalidatesTags: (res, err, arg) => arg.outputIds.map(outputId => ({ type: 'output-graph' as const, id: outputId })), 144 | }), 145 | }), 146 | }); 147 | 148 | export const { 149 | useGetProjectOutputGraphQuery, useStartTransformingMutation, useDeleteProjectOutputMutation, useDeleteProjectOutputsMutation, 150 | } = projectOutputsApi; 151 | -------------------------------------------------------------------------------- /src/features/outputs/outputsSlice.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createSlice } from '@reduxjs/toolkit'; 18 | import { PayloadAction } from '@reduxjs/toolkit'; 19 | import { RootState } from '../../app/store'; 20 | import { IQAStep, IQuestion } from './qa/types'; 21 | 22 | export interface TransformationStatus { 23 | outputId: string; 24 | projectId: string; 25 | workspaceId: string; 26 | steps: Array; 27 | error?: string; 28 | } 29 | 30 | export interface OutputsState { 31 | transformationStatuses: { [k: string]: TransformationStatus }, 32 | currentStatusId: string; 33 | } 34 | 35 | const initialState: OutputsState = { 36 | transformationStatuses: {}, 37 | currentStatusId: '', 38 | }; 39 | 40 | export const outputsSlice = createSlice({ 41 | name: 'outputs', 42 | initialState, 43 | reducers: { 44 | setCurrentStatusId: (state, action: PayloadAction) => { 45 | state.currentStatusId = action.payload; 46 | }, 47 | createStatus: (state, action: PayloadAction<{ workspaceId: string, projectId: string, outputId: string }>) => { 48 | if (!(action.payload.outputId in state.transformationStatuses)) { 49 | state.transformationStatuses[action.payload.outputId] = { ...action.payload, steps: [] }; 50 | } 51 | state.currentStatusId = action.payload.outputId; 52 | }, 53 | addQAStep: (state, action: PayloadAction<{ id: string, step: IQAStep }>) => { 54 | if (action.payload.id in state.transformationStatuses) { 55 | const status = state.transformationStatuses[action.payload.id]; 56 | status.steps.push(action.payload.step); 57 | } 58 | }, 59 | updateQAStep: (state, action: PayloadAction<{ id: string, stepIdx: number, question: IQuestion }>) => { 60 | const { id, stepIdx, question } = action.payload; 61 | if (id in state.transformationStatuses) { 62 | const status = state.transformationStatuses[id]; 63 | if (stepIdx >= 0 && stepIdx < status.steps.length) { 64 | status.steps[stepIdx].question = question; 65 | } 66 | } 67 | }, 68 | setError: (state, action: PayloadAction<{ id: string, error: string }>) => { 69 | if (action.payload.id in state.transformationStatuses) state.transformationStatuses[action.payload.id].error = action.payload.error; 70 | }, 71 | deleteStatus: (state, action: PayloadAction) => { 72 | delete state.transformationStatuses[action.payload]; 73 | }, 74 | deleteStatusesWithErrors: (state) => { 75 | const noErrorKeys = Object.keys(state.transformationStatuses).filter(k => !state.transformationStatuses[k].error); 76 | state.transformationStatuses = Object.fromEntries(noErrorKeys.map(k => [k, state.transformationStatuses[k]])); 77 | }, 78 | } 79 | }); 80 | 81 | export const { setCurrentStatusId, createStatus, addQAStep, updateQAStep, setError, deleteStatus, deleteStatusesWithErrors } = outputsSlice.actions; 82 | export const selectTransformationStatus = (id: string) => (state: RootState): (TransformationStatus | undefined) => state.outputs.transformationStatuses[id]; 83 | export const selectTransformationStatuses = (state: RootState): ({ [k: string]: TransformationStatus }) => state.outputs.transformationStatuses; 84 | export const selectCurrentStatusId = (state: RootState): string => state.outputs.currentStatusId; 85 | export const selectCurrentStatus = (state: RootState): (TransformationStatus | undefined) => state.outputs.transformationStatuses[state.outputs.currentStatusId]; 86 | 87 | export default outputsSlice.reducer; 88 | -------------------------------------------------------------------------------- /src/features/outputs/qa/Confirm.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { FunctionComponent } from 'react'; 18 | import { Hints } from './Hints'; 19 | import { IQASubComponentProps } from './types'; 20 | import { Radio, TextContent } from '@patternfly/react-core'; 21 | 22 | export const Confirm: FunctionComponent = ({ isDisabled, question, setAnswer }) => { 23 | isDisabled = isDisabled || false; 24 | const onChange = (checked: boolean, event: React.FormEvent): void => { 25 | if (!checked) return; 26 | setAnswer((event.target as HTMLInputElement).value === 'true'); 27 | }; 28 | return ( 29 |
30 | {question.description} 31 | 41 | 51 | {question.hints?.length && } 52 |
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /src/features/outputs/qa/Hints.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { FunctionComponent } from "react"; 18 | 19 | interface HintsProps { 20 | hints: Array; 21 | } 22 | 23 | export const Hints: FunctionComponent = ({ hints }) => { 24 | return ( 25 |
26 |

Hints:

27 | {hints.map((hint, i) => ( 28 |
{hint}
29 | ))} 30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/features/outputs/qa/Input.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { FunctionComponent } from "react"; 18 | import { Hints } from './Hints'; 19 | import { IQASubComponentProps } from './types'; 20 | import { TextContent, TextInput } from '@patternfly/react-core'; 21 | 22 | export const Input: FunctionComponent = ({ isDisabled, question, setAnswer }) => { 23 | return ( 24 |
25 | {question.description} 26 | setAnswer(value)} 32 | /> 33 | {question.hints?.length && } 34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/features/outputs/qa/MultiLineInput.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { FunctionComponent } from 'react'; 18 | import { Hints } from './Hints'; 19 | import { IQASubComponentProps } from './types'; 20 | import { TextContent, TextArea } from '@patternfly/react-core'; 21 | 22 | export const MultiLineInput: FunctionComponent = ({ isDisabled, question, setAnswer }) => { 23 | isDisabled = isDisabled || false; 24 | return ( 25 |
26 | {question.description} 27 | 46 | ); 47 | 48 | export const Plan: FunctionComponent = ({ 49 | workspaceId, projectId, isDisabled, projectHasPlan, projectHasStalePlan, refetch, 50 | }) => { 51 | isDisabled = isDisabled ?? false; 52 | const { 53 | data: planObj, 54 | isLoading: isGettingPlan, 55 | isFetching: isGettingPlanAgain, 56 | error: getPlanErr, 57 | } = useReadPlanQuery({ wid: workspaceId, pid: projectId }, { skip: isDisabled }); 58 | const [startPlanning, { isLoading: isPlanStarting, error: startPlanError }] = useStartPlanningMutation(); 59 | const [updatePlan, { isLoading: isUpdatingPlan, error: updatePlanError }] = useUpdatePlanMutation(); 60 | const [currentPlan, setCurrentPlan] = useState(planObj?.plan || ''); 61 | const planProgressStatus = useAppSelector(selectPlanProgressStatus(workspaceId, projectId)); 62 | 63 | useEffect(() => { 64 | setCurrentPlan(planObj?.plan ?? ''); 65 | try { 66 | if (refetch) refetch(); 67 | } catch (e) { 68 | console.log(`${e}`); 69 | } 70 | }, [planObj?.plan, refetch]); 71 | 72 | return ( 73 | 74 | 75 | Plan 76 |
77 | 78 | 79 | 87 | 88 | 89 | 95 | 96 | {(isGettingPlan || isGettingPlanAgain) && <> 97 | 98 | 99 | 100 | {planProgressStatus && ( 101 | 102 | 103 | 104 | )} 105 | } 106 | 107 |
108 | {startPlanError && <>
} 109 | {updatePlanError && <>
} 110 | {!isDisabled && projectHasPlan && projectHasStalePlan && <> 111 | 114 |
115 | } 116 | {(isGettingPlan || isGettingPlanAgain) ? ( 117 | 118 | ) : getPlanErr ? ( 119 | <> 120 | {(getPlanErr as { status: number })?.status !== 404 && } 121 | 122 | 123 | ) : planObj ? ( 124 |
125 | setCurrentPlan(p)} 129 | highlight={code => hightlightWithLineNumbers(code)} 130 | padding={10} 131 | textareaId="codeArea" 132 | className="editor" 133 | style={{ 134 | fontFamily: '"Fira code", "Fira Mono", monospace', 135 | fontSize: 18, 136 | outline: 0 137 | }} 138 | /> 139 |
140 | ) : ( 141 | 142 | )} 143 |
144 |
145 | ); 146 | }; 147 | -------------------------------------------------------------------------------- /src/features/plan/planApi.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'; 18 | import { API_BASE } from '../common/constants'; 19 | import { sleep } from '../common/utils'; 20 | import { deletePlanProgressStatus, setPlanProgressStatus } from './planSlice'; 21 | 22 | const PLAN_POLLING_INTERVAL_MS = 3000; 23 | 24 | export const planApi = createApi({ 25 | reducerPath: 'planApi', 26 | baseQuery: fetchBaseQuery({ 27 | baseUrl: API_BASE, 28 | prepareHeaders: (headers) => { 29 | headers.set('Content-Type', 'application/json'); 30 | headers.set('Accept', 'application/json'); 31 | return headers; 32 | } 33 | }), 34 | tagTypes: ['plan'], 35 | endpoints: (builder) => ({ 36 | startPlanning: builder.mutation({ 37 | query: ({ wid, pid }) => ({ 38 | url: `/workspaces/${wid}/projects/${pid}/plan`, 39 | method: 'POST', 40 | }), 41 | invalidatesTags: (result, error, arg) => [{ type: 'plan', id: arg.pid }], 42 | }), 43 | readPlan: builder.query<{ plan: string }, { wid: string, pid: string }>({ 44 | queryFn: async ({ wid, pid }, baseQueryApi, __, _fetchWithBQ) => { 45 | const url = `${API_BASE}/workspaces/${wid}/projects/${pid}/plan`; 46 | let finalStatus = 404; 47 | baseQueryApi.dispatch(setPlanProgressStatus({ workspaceId: wid, projectId: pid, files: 0, transformers: 0 })); 48 | try { 49 | let res = await fetch(url); 50 | finalStatus = res.status; 51 | if (!res.ok) { 52 | throw new Error(`got an error status code: ${res.status} ${res.statusText}`); 53 | } 54 | while (res.status === 204 || res.status === 202) { 55 | await sleep(PLAN_POLLING_INTERVAL_MS); 56 | res = await fetch(url); 57 | finalStatus = res.status; 58 | if (!res.ok) { 59 | throw new Error(`got an error status code: ${res.status} ${res.statusText}`); 60 | } 61 | if (res.status === 202) { 62 | const data: { files: number, transformers: number } = await res.json(); 63 | baseQueryApi.dispatch(setPlanProgressStatus({ 64 | workspaceId: wid, 65 | projectId: pid, 66 | files: data.files, 67 | transformers: data.transformers, 68 | })); 69 | } 70 | } 71 | const data: { plan: string } = await res.json(); 72 | baseQueryApi.dispatch(deletePlanProgressStatus({ workspaceId: wid, projectId: pid})); 73 | return { data }; 74 | } catch (e) { 75 | console.error(e); 76 | baseQueryApi.dispatch(deletePlanProgressStatus({ workspaceId: wid, projectId: pid})); 77 | return { error: { status: finalStatus, data: `${e}` } as FetchBaseQueryError }; 78 | } 79 | }, 80 | providesTags: (result, error, arg) => [{ type: 'plan', id: arg.pid }], 81 | }), 82 | updatePlan: builder.mutation({ 83 | query: ({ wid, pid, plan }) => ({ 84 | url: `/workspaces/${wid}/projects/${pid}/plan`, 85 | method: 'PUT', 86 | body: { plan }, 87 | }), 88 | invalidatesTags: (result, error, arg) => [{ type: 'plan', id: arg.pid }], 89 | }), 90 | }), 91 | }); 92 | 93 | export const { useReadPlanQuery, useStartPlanningMutation, useUpdatePlanMutation } = planApi; 94 | -------------------------------------------------------------------------------- /src/features/plan/planSlice.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createSlice } from '@reduxjs/toolkit'; 18 | import { PayloadAction } from '@reduxjs/toolkit'; 19 | import { RootState } from '../../app/store'; 20 | 21 | export interface PlanProgressStatus { 22 | id: string; 23 | files: number; 24 | transformers: number; 25 | } 26 | 27 | export interface InputsState { 28 | planProgressStatuses: { [k: string]: PlanProgressStatus }, 29 | } 30 | 31 | const initialState: InputsState = { 32 | planProgressStatuses: {}, 33 | }; 34 | 35 | export const getId = (workspaceId: string, projectId: string): string => `${workspaceId}-${projectId}`; 36 | 37 | export const planSlice = createSlice({ 38 | name: 'plan', 39 | initialState, 40 | reducers: { 41 | setPlanProgressStatus: (state, action: PayloadAction<{ workspaceId: string, projectId: string, files: number, transformers: number }>) => { 42 | const id = getId(action.payload.workspaceId, action.payload.projectId); 43 | state.planProgressStatuses[id] = { 44 | id, 45 | files: action.payload.files, 46 | transformers: action.payload.transformers, 47 | }; 48 | }, 49 | deletePlanProgressStatus: (state, action: PayloadAction<{ workspaceId: string, projectId: string }>) => { 50 | delete state.planProgressStatuses[getId(action.payload.workspaceId, action.payload.projectId)]; 51 | }, 52 | } 53 | }); 54 | 55 | export const { setPlanProgressStatus, deletePlanProgressStatus } = planSlice.actions; 56 | export const selectPlanProgressStatus = (wid: string, pid: string) => (state: RootState): (PlanProgressStatus | undefined) => state.plan.planProgressStatuses[getId(wid, pid)]; 57 | 58 | export default planSlice.reducer; 59 | -------------------------------------------------------------------------------- /src/features/projects/Project.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { Alert, PageSection, Spinner, Title, Split, SplitItem } from "@patternfly/react-core"; 18 | import { FunctionComponent } from "react"; 19 | import { useParams } from "react-router-dom"; 20 | import { useReadWorkspaceQuery } from "../workspaces/workspacesApi"; 21 | import { useReadProjectQuery } from "./projectsApi"; 22 | import { Inputs } from '../inputs/Inputs'; 23 | import { Plan } from "../plan/Plan"; 24 | import { Outputs } from "../outputs/Outputs"; 25 | import { extractErrMsg } from "../common/utils"; 26 | import { selectPlanProgressStatus } from "../plan/planSlice"; 27 | import { useAppSelector } from "../../app/hooks"; 28 | 29 | export const Project: FunctionComponent = () => { 30 | const { workspaceId, projectId, outputId } = useParams(); 31 | const currentWorkspaceId: string = workspaceId ?? ''; 32 | const currentProjectId: string = projectId ?? ''; 33 | const currentOutputId: string = outputId ?? ''; 34 | const { data: currentWorkspace } = useReadWorkspaceQuery(currentWorkspaceId); 35 | const { data: currentProject, isLoading, error, refetch: refetchProject } = useReadProjectQuery({ wid: currentWorkspaceId, pid: currentProjectId }); 36 | const statuses = currentProject ? Object.entries(currentProject.status || {}).filter(([_k, v]) => (v)).map(([k, _v]) => k) : []; 37 | const projectHasInputs = currentProject && Object.keys(currentProject.inputs ?? {}).length > 0; 38 | const projectHasPlan = currentProject && Object.entries(currentProject.status ?? {}).some(([k, v]) => v && k === 'plan'); 39 | const projectHasStalePlan = currentProject && Object.entries(currentProject.status ?? {}).some(([k, v]) => v && k === 'stale_plan'); 40 | const planProgressStatus = useAppSelector(selectPlanProgressStatus(workspaceId ?? '', projectId ?? '')); 41 | const isPlanning = Boolean(currentProject && planProgressStatus); 42 | 43 | return ( 44 | 45 | { 46 | error ? ( 47 | 48 | ) : isLoading ? ( 49 | 50 | ) : currentProject ? (<> 51 | Workspace {currentWorkspace?.name ?? currentWorkspaceId} - Project {currentProject.name ?? currentProjectId} 52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 |
ID{currentProject.id}
Name{currentProject.name}
Description{currentProject.description}
Created at{`${new Date(currentProject.timestamp)}`}
Status{statuses.map((status) => ( 60 | {status} 61 | ))}
64 |
65 | 69 |
70 | 77 |
78 | 85 | 86 | ) : null 87 | } 88 |
89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /src/features/projects/projectsApi.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'; 18 | import { API_BASE } from '../common/constants'; 19 | import { IMetadata, IProjectInput, IProjectOutput } from '../common/types'; 20 | 21 | export interface IProject extends IMetadata { 22 | inputs?: { [id: string]: IProjectInput }; 23 | outputs?: { [id: string]: IProjectOutput }; 24 | status?: { [status: string]: boolean }; 25 | } 26 | 27 | export const projectsApi = createApi({ 28 | reducerPath: 'projectsApi', 29 | baseQuery: fetchBaseQuery({ 30 | baseUrl: API_BASE, 31 | prepareHeaders: (headers) => { 32 | headers.set('Content-Type', 'application/json'); 33 | headers.set('Accept', 'application/json'); 34 | return headers; 35 | } 36 | }), 37 | tagTypes: ['projects'], 38 | endpoints: (builder) => ({ 39 | listProjects: builder.query, string>({ 40 | query: (id) => `/workspaces/${id}/projects`, 41 | providesTags: (result, _error, _arg) => ( 42 | result 43 | ? [...result.map(({ id }) => ({ type: 'projects' as const, id })), { type: 'projects', id: 'list' }] 44 | : [{ type: 'projects', id: 'list' }] 45 | ), 46 | }), 47 | createProject: builder.mutation<{ id: string }, { wid: string, project: IProject }>({ 48 | query: ({ wid, project }) => ({ 49 | url: `/workspaces/${wid}/projects`, 50 | method: 'POST', 51 | body: project, 52 | }), 53 | invalidatesTags: [{ type: 'projects', id: 'list' }], 54 | }), 55 | readProject: builder.query({ 56 | query: ({ wid, pid }) => `/workspaces/${wid}/projects/${pid}`, 57 | providesTags: (result, error, arg) => [{ type: 'projects' as const, id: arg.pid }], 58 | }), 59 | updateProject: builder.mutation({ 60 | query: ({ wid, project }) => ({ 61 | url: `/workspaces/${wid}/projects/${project.id}`, 62 | method: 'PUT', 63 | body: project, 64 | }), 65 | invalidatesTags: (result, error, arg) => [{ type: 'projects' as const, id: 'list' }, { type: 'projects' as const, id: arg.project.id }], 66 | }), 67 | deleteProject: builder.mutation({ 68 | query: ({ wid, pid }) => ({ 69 | url: `/workspaces/${wid}/projects/${pid}`, 70 | method: 'DELETE', 71 | }), 72 | invalidatesTags: (result, error, arg) => [{ type: 'projects' as const, id: 'list' }, { type: 'projects' as const, id: arg.pid }], 73 | }), 74 | deleteProjects: builder.mutation }>({ 75 | queryFn: async ({ wid, pids }, _queryApi, _extraOptions, fetchWithBQ) => { 76 | const results = await Promise.all(pids.map(pid => fetchWithBQ({ 77 | url: `/workspaces/${wid}/projects/${pid}`, 78 | method: 'DELETE', 79 | }))); 80 | const foundResult = results.find(result => result.error); 81 | if (foundResult) return { error: foundResult.error as FetchBaseQueryError }; 82 | return { data: undefined }; 83 | }, 84 | invalidatesTags: (result, error, arg) => [ 85 | { type: 'projects' as const, id: 'list' }, 86 | ...arg.pids.map(pid => ({ type: 'projects' as const, id: pid })), 87 | ], 88 | }), 89 | }), 90 | }); 91 | 92 | export const { useListProjectsQuery, useCreateProjectMutation, useReadProjectQuery, useUpdateProjectMutation, 93 | useDeleteProjectMutation, useDeleteProjectsMutation } = projectsApi; 94 | -------------------------------------------------------------------------------- /src/features/support/Support.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { FunctionComponent } from "react"; 18 | import { PageSection, Title, Alert, Spinner } from '@patternfly/react-core'; 19 | import { ReactComponent as Logo } from '../../assets/images/logo.svg'; 20 | import { useGetSupportInfoQuery } from "./supportApi"; 21 | import { extractErrMsg } from "../common/utils"; 22 | 23 | export const Support: FunctionComponent = () => { 24 | const { data, isLoading, error } = useGetSupportInfoQuery(); 25 | return ( 26 | 27 |
28 |
29 | Support Information 30 |

Documentation and tutorials can be found at https://move2kube.konveyor.io/

31 |

Please provide this info while creating issues at https://github.com/konveyor/move2kube/issues

32 |
33 |
34 | { 35 | error ? ( 36 | 37 | ) : isLoading ? ( 38 | 39 | ) : data ? ( 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
CLI
{data.cli_version}
API
{data.api_version}
UI
{data.ui_version}
Docker
{data.docker}
48 | ) : null 49 | } 50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/features/support/supportApi.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; 18 | import { API_BASE } from '../common/constants'; 19 | 20 | export interface ISupportInfo { 21 | cli_version: string; 22 | api_version: string; 23 | ui_version: string; 24 | docker: string; 25 | } 26 | 27 | export const supportApi = createApi({ 28 | reducerPath: 'supportApi', 29 | baseQuery: fetchBaseQuery({ 30 | baseUrl: API_BASE, 31 | prepareHeaders: (headers) => { 32 | headers.set('Content-Type', 'application/json'); 33 | headers.set('Accept', 'application/json'); 34 | return headers; 35 | } 36 | }), 37 | tagTypes: ['support'], 38 | endpoints: (builder) => ({ 39 | getSupportInfo: builder.query({ 40 | query: () => '/support', 41 | providesTags: ['support'], 42 | }), 43 | }) 44 | 45 | }); 46 | 47 | export const { useGetSupportInfoQuery } = supportApi; 48 | -------------------------------------------------------------------------------- /src/features/toasts/Toasts.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { AlertGroup } from "@patternfly/react-core"; 18 | import { FunctionComponent } from "react"; 19 | 20 | export const Toasts: FunctionComponent = () => { 21 | return ( 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/features/toasts/toastsSlice.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 18 | import { RootState } from "../../app/store"; 19 | 20 | export type ToastVariant = 'success' | 'danger' | 'warning' | 'info' | 'default'; 21 | 22 | export interface IToast { 23 | id: number; 24 | variant: ToastVariant; 25 | message: string; 26 | } 27 | 28 | export interface IToastsState { 29 | nextId: number; 30 | toasts: Array; 31 | } 32 | 33 | const initialState: IToastsState = { 34 | nextId: 0, 35 | toasts: [], 36 | }; 37 | 38 | export const toastsSlice = createSlice({ 39 | name: 'toasts', 40 | initialState, 41 | reducers: { 42 | createToast: (state, action: PayloadAction) => { 43 | state.toasts.push({ ...action.payload, id: state.nextId }); 44 | state.nextId++; 45 | }, 46 | deleteToast: (state, action: PayloadAction) => { 47 | state.toasts = state.toasts.filter(t => t.id !== action.payload); 48 | }, 49 | deleteAllToasts: (state) => { 50 | state.toasts = []; 51 | }, 52 | }, 53 | }); 54 | 55 | export const selectToasts = (state: RootState): Array => state.toasts.toasts; 56 | 57 | export const { createToast, deleteToast, deleteAllToasts } = toastsSlice.actions; 58 | 59 | export default toastsSlice.reducer; 60 | -------------------------------------------------------------------------------- /src/features/workspaces/workspacesApi.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'; 18 | import { API_BASE } from '../common/constants'; 19 | import { IMetadata, IProjectInput } from '../common/types'; 20 | 21 | export interface IWorkspace extends IMetadata { 22 | project_ids?: Array; 23 | inputs?: { [id: string]: IWorkspaceInput }; 24 | } 25 | 26 | type IWorkspaceInput = IProjectInput; 27 | 28 | export const workspacesApi = createApi({ 29 | reducerPath: 'workspacesApi', 30 | baseQuery: fetchBaseQuery({ 31 | baseUrl: API_BASE, 32 | prepareHeaders: (headers) => { 33 | headers.set('Content-Type', 'application/json'); 34 | headers.set('Accept', 'application/json'); 35 | return headers; 36 | } 37 | }), 38 | tagTypes: ['workspaces'], 39 | endpoints: (builder) => ({ 40 | listWorkspaces: builder.query, void>({ 41 | query: () => '/workspaces', 42 | providesTags: (result, _error, _arg) => ( 43 | result 44 | ? [...result.map(({ id }) => ({ type: 'workspaces' as const, id })), { type: 'workspaces', id: 'list' }] 45 | : [{ type: 'workspaces', id: 'list' }] 46 | ), 47 | }), 48 | createWorkspace: builder.mutation<{ id: string }, IWorkspace>({ 49 | query: (w) => ({ 50 | url: '/workspaces', 51 | method: 'POST', 52 | body: w, 53 | }), 54 | invalidatesTags: [{ type: 'workspaces', id: 'list' }], 55 | }), 56 | readWorkspace: builder.query({ 57 | query: (id) => `/workspaces/${id}`, 58 | providesTags: (result, error, arg) => [{ type: 'workspaces', id: arg }], 59 | }), 60 | updateWorkspace: builder.mutation({ 61 | query: (w) => ({ 62 | url: `/workspaces/${w.id}`, 63 | method: 'PUT', 64 | body: w, 65 | }), 66 | invalidatesTags: (result, error, arg) => [{ type: 'workspaces', id: arg.id }], 67 | }), 68 | deleteWorkspace: builder.mutation({ 69 | query: (id: string) => ({ 70 | url: `/workspaces/${id}`, 71 | method: 'DELETE', 72 | }), 73 | invalidatesTags: (result, error, arg) => [{ type: 'workspaces', id: 'list' }, { type: 'workspaces', id: arg }], 74 | }), 75 | deleteWorkspaces: builder.mutation>({ 76 | queryFn: async (ids, _queryApi, _extraOptions, fetchWithBQ) => { 77 | const results = await Promise.all(ids.map(id => fetchWithBQ({ 78 | url: `/workspaces/${id}`, 79 | method: 'DELETE', 80 | }))); 81 | const foundResult = results.find(result => result.error); 82 | if (foundResult) return { error: foundResult.error as FetchBaseQueryError }; 83 | return { data: undefined }; 84 | }, 85 | invalidatesTags: (result, error, arg) => [ 86 | { type: 'workspaces', id: 'list' }, 87 | ...arg.map(wid => ({ type: 'workspaces' as const, id: wid })), 88 | ], 89 | }), 90 | }) 91 | 92 | }); 93 | 94 | export const { useListWorkspacesQuery, useCreateWorkspaceMutation, useReadWorkspaceQuery, useUpdateWorkspaceMutation, 95 | useDeleteWorkspaceMutation, useDeleteWorkspacesMutation } = workspacesApi; 96 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | :root { 18 | --table-row-odd-bg-color: var(--pf-global--palette--black-200); 19 | --my-chip-color: var(--pf-global--primary-color--100); 20 | } 21 | 22 | * { 23 | box-sizing: border-box; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | height: 100vh; 29 | } 30 | 31 | #root { 32 | height: 100%; 33 | } 34 | 35 | .table-row-odd { 36 | background-color: var(--table-row-odd-bg-color); 37 | } 38 | 39 | .project-output-graph-modal { 40 | height: 90vh; 41 | } 42 | 43 | .on-hover { 44 | overflow-wrap: break-word; 45 | } 46 | 47 | .on-hover-child { 48 | display: none; 49 | } 50 | 51 | .on-hover:hover .on-hover-child { 52 | display: block; 53 | } 54 | 55 | /* utility classes */ 56 | 57 | .margin-left-auto { 58 | margin-left: auto; 59 | } 60 | 61 | .flex-vertical { 62 | display: flex; 63 | flex-direction: column; 64 | } 65 | 66 | .center { 67 | justify-content: center; 68 | align-content: center; 69 | align-items: center; 70 | } 71 | 72 | .padding-1em { 73 | padding: 1em; 74 | } 75 | 76 | .my-table tbody { 77 | width: 100%; 78 | } 79 | 80 | .my-table td:last-child { 81 | width: 100%; 82 | } 83 | 84 | .my-table td { 85 | box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.5); 86 | padding: 1em; 87 | background-color: white; 88 | } 89 | 90 | .my-chip { 91 | border: 2px solid var(--my-chip-color); 92 | padding: 0 .5em; 93 | border-radius: 1em; 94 | } 95 | 96 | /* react-simple-code-editor */ 97 | /* https://github.com/react-simple-code-editor/react-simple-code-editor/issues/42#issuecomment-569183148 */ 98 | 99 | .editor { 100 | counter-reset: line; 101 | border: 1px solid #ced4da; 102 | } 103 | 104 | .editor #codeArea { 105 | outline: none; 106 | padding-left: 60px !important; 107 | } 108 | 109 | .editor pre { 110 | padding-left: 60px !important; 111 | } 112 | 113 | .editor .editorLineNumber { 114 | position: absolute; 115 | left: 0px; 116 | color: #cccccc; 117 | text-align: right; 118 | width: 40px; 119 | font-weight: 100; 120 | } 121 | 122 | .wrap-editor { 123 | height: 17em; 124 | overflow-y: scroll; 125 | border: 1px solid rgba(0, 0, 0, 0.2); 126 | } 127 | 128 | .align-items-center { 129 | align-items: center; 130 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corporation 2023 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import '@patternfly/react-core/dist/styles/base.css'; 18 | import React from 'react'; 19 | import { createRoot } from 'react-dom/client'; 20 | import { Provider } from 'react-redux'; 21 | import { store } from './app/store'; 22 | import { App } from './App'; 23 | import { BrowserRouter } from 'react-router-dom'; 24 | import './index.css'; 25 | 26 | const root = createRoot(document.getElementById('root') as HTMLDivElement); 27 | root.render( 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const { createProxyMiddleware } = require('http-proxy-middleware'); 2 | 3 | module.exports = function(app) { 4 | app.use( 5 | '/api/v1', 6 | createProxyMiddleware({ 7 | target: 'http://localhost:8888', 8 | changeOrigin: true, 9 | }) 10 | ); 11 | app.use( 12 | '/auth', 13 | createProxyMiddleware({ 14 | target: 'http://localhost:8888', 15 | changeOrigin: true, 16 | }) 17 | ); 18 | app.use( 19 | '/auth-server', 20 | createProxyMiddleware({ 21 | target: 'http://localhost:8888', 22 | changeOrigin: true, 23 | }) 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------