diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b405b47cb6be68df9ed8e7d7941244f2cc22f643..cab254a8cead5777e77d2180acf280bf8cdfdbab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: node:16 +image: node:18 cache: paths: - node_modules diff --git a/Dockerfile b/Dockerfile index a4eba23cfd2e33d53d8ed21c5b0c529bb034a520..831ee92da98a9c44ebee34cd9920c5db395bbf85 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # build stage -FROM node:16 as build-stage +FROM node:18 as build-stage WORKDIR /app HEALTHCHECK --interval=35s --timeout=4s CMD curl -f http://localhost || exit 1 # RUN apk add yarn @@ -11,7 +11,7 @@ RUN npm run build-only # production stage FROM nginx:stable-alpine as production-stage -HEALTHCHECK --interval=35s --timeout=4s CMD curl --head -f http://localhost || exit 1 +HEALTHCHECK --interval=305s --timeout=4s CMD curl --head -f http://localhost || exit 1 COPY --from=build-stage /app/dist /usr/share/nginx/html COPY --from=build-stage /app/src/assets/env.template.js /tmp COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/package-lock.json b/package-lock.json index deb819d45a851e4ac379e5281b1efa41693a6284..03f8850644a83ad975e37a422fac5b51b4c851d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "@esbuild-plugins/node-globals-polyfill": "~0.2.3", "@esbuild-plugins/node-modules-polyfill": "~0.2.2", "@rushstack/eslint-patch": "~1.2.0", - "@tsconfig/node16": "^16.1.1", + "@tsconfig/node18": "^18.2.1", "@types/bootstrap": "~5.2.6", "@types/dompurify": "~3.0.2", "@types/node": "^16.18.48", @@ -2051,10 +2051,10 @@ "node": ">=14.0.0" } }, - "node_modules/@tsconfig/node16": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.1.tgz", - "integrity": "sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==", + "node_modules/@tsconfig/node18": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.1.tgz", + "integrity": "sha512-RDDZFuofwkcKpl8Vpj5wFbY+H53xOtqK7ckEL1sXsbPwvKwDdjQf3LkHbtt9sxIHn9nWIEwkmCwBRZ6z5TKU2A==", "dev": true }, "node_modules/@types/bootstrap": { @@ -8099,10 +8099,10 @@ "tslib": "^2.5.0" } }, - "@tsconfig/node16": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.1.tgz", - "integrity": "sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==", + "@tsconfig/node18": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.1.tgz", + "integrity": "sha512-RDDZFuofwkcKpl8Vpj5wFbY+H53xOtqK7ckEL1sXsbPwvKwDdjQf3LkHbtt9sxIHn9nWIEwkmCwBRZ6z5TKU2A==", "dev": true }, "@types/bootstrap": { diff --git a/package.json b/package.json index 41c9d17529e470fab6593c1a7cff452aaa8d6d6c..41e4a72dcd15f579ef85f2d0676dd54e63b19bfd 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@esbuild-plugins/node-globals-polyfill": "~0.2.3", "@esbuild-plugins/node-modules-polyfill": "~0.2.2", "@rushstack/eslint-patch": "~1.2.0", - "@tsconfig/node16": "^16.1.1", + "@tsconfig/node18": "^18.2.1", "@types/bootstrap": "~5.2.6", "@types/dompurify": "~3.0.2", "@types/node": "^16.18.48", diff --git a/src/components/workflows/modals/ArbitraryWorkflowModal.vue b/src/components/workflows/modals/ArbitraryWorkflowModal.vue index 87952e20e32a58277dbf9a1ea052a93b4fc885a7..9abbaf2783fa0d6868361493de778f1114a70f86 100644 --- a/src/components/workflows/modals/ArbitraryWorkflowModal.vue +++ b/src/components/workflows/modals/ArbitraryWorkflowModal.vue @@ -1,20 +1,22 @@ <script setup lang="ts"> import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; -import { computed, onMounted, reactive, ref } from "vue"; +import { computed, onMounted, reactive, ref, watch } from "vue"; import { useRouter } from "vue-router"; import { GitRepository, requiredRepositoryFiles, determineGitIcon, } from "@/utils/GitRepository"; -import { Modal } from "bootstrap"; +import { Collapse, Modal } from "bootstrap"; const props = defineProps<{ modalID: string; }>(); let createWorkflowModal: Modal | null = null; +let privateRepositoryCollapse: Collapse | null = null; +let tokenHelpCollapse: Collapse | null = null; const arbitraryWorkflowForm = ref<HTMLFormElement | undefined>(undefined); const workflowRepositoryElement = ref<HTMLInputElement | undefined>(undefined); const router = useRouter(); @@ -27,6 +29,14 @@ const workflow = reactive<{ git_commit_hash: "", }); +const repositoryCredentials = reactive<{ + token: string; + privateRepo: boolean; +}>({ + token: "", + privateRepo: false, +}); + const formState = reactive<{ loading: boolean; checkRepoLoading: boolean; @@ -43,8 +53,21 @@ const formState = reactive<{ unsupportedRepository: false, }); +watch( + () => repositoryCredentials.privateRepo, + (show) => { + if (show) { + privateRepositoryCollapse?.show(); + } else { + privateRepositoryCollapse?.hide(); + tokenHelpCollapse?.hide(); + } + }, +); + function modalClosed() { formState.validated = false; + tokenHelpCollapse?.hide(); } function viewWorkflow() { @@ -54,6 +77,10 @@ function viewWorkflow() { query: { repository: encodeURI(workflow.repository_url), commit_hash: workflow.git_commit_hash, + token: + repositoryCredentials.token.length > 0 + ? encodeURIComponent(repositoryCredentials.token) + : undefined, }, }); } @@ -68,6 +95,9 @@ function checkRepository() { const repo = GitRepository.buildRepository( workflow.repository_url, workflow.git_commit_hash, + repositoryCredentials.privateRepo + ? repositoryCredentials.token + : undefined, ); repo .checkFilesExist(requiredRepositoryFiles, true) @@ -93,12 +123,19 @@ function checkRepository() { } } } + const gitIcon = computed<string>(() => determineGitIcon(workflow.repository_url), ); onMounted(() => { createWorkflowModal = new Modal("#" + props.modalID); + privateRepositoryCollapse = new Collapse("#privateRepositoryCollapse", { + toggle: false, + }); + tokenHelpCollapse = new Collapse("#tokenHelpCollapse", { + toggle: false, + }); }); </script> @@ -176,6 +213,73 @@ onMounted(() => { </li> </ul> </div> + <div class="mb-3"> + <div class="form-check fs-5"> + <input + class="form-check-input" + type="checkbox" + v-model="repositoryCredentials.privateRepo" + id="privateRepositoryCheckbox" + @change="formState.allowUpload = false" + aria-controls="#privateRepositoryCollapse" + /> + <label class="form-check-label" for="privateRepositoryCheckbox"> + Private Git Repository + </label> + </div> + <div class="collapse" id="privateRepositoryCollapse"> + <label for="tokenUsernameInput" class="form-label">Token</label> + <div class="input-group"> + <div class="input-group-text"> + <font-awesome-icon icon="fa-solid fa-key" /> + </div> + <input + type="password" + class="form-control" + id="repositoryTokenInput" + v-model="repositoryCredentials.token" + @change="formState.allowUpload = false" + :required="repositoryCredentials.privateRepo" + aria-controls="#tokenHelpCollapse" + /> + <div + class="input-group-text cursor-pointer hover-info" + @click="tokenHelpCollapse?.toggle()" + > + <font-awesome-icon icon="fa-solid fa-circle-question" /> + </div> + </div> + <div class="collapse" id="tokenHelpCollapse"> + <div class="card card-body mt-3"> + <h5>GitHub</h5> + <p> + For private GitHub repositories, CloWM needs a Personal Access + Token (classic) with the scope <code>repo</code>.<br /> + Read this + <a + target="_blank" + href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic" + >Tutorial</a + > + on how to create such a token. + </p> + <h5>GitLab</h5> + <p> + For private GitLab repositories, CloWM needs a Project Access + Token with the <code>read_api</code> scope and at least + <code>Reporter</code> role.<br /> + Read this + <a + target="_blank" + href="https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html#create-a-project-access-token" + >Tutorial</a + > + on how to create such a token. + </p> + </div> + </div> + </div> + </div> </form> </template> <template v-slot:footer> @@ -215,4 +319,8 @@ onMounted(() => { </bootstrap-modal> </template> -<style scoped></style> +<style scoped> +.hover-info:hover { + color: var(--bs-info) !important; +} +</style> diff --git a/src/router/index.ts b/src/router/index.ts index 8abd1c20ea0b19fd0cc8c0381e86aeb4e2e7fcc2..cd6d25646eb4b2ff556af43ba1b667106b4e973a 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -60,6 +60,7 @@ const router = createRouter({ props: (route) => ({ repository: route.query.repository, commit_hash: route.query.commit_hash, + token: route.query.token, }), }, { diff --git a/src/utils/GitRepository.ts b/src/utils/GitRepository.ts index 6baffa13c27556bb42e11c22f2d9756f35ea6188..68004e6c06e19000f3710ff2db33f8430c200301 100644 --- a/src/utils/GitRepository.ts +++ b/src/utils/GitRepository.ts @@ -57,7 +57,7 @@ export abstract class GitRepository { // eslint-disable-next-line @typescript-eslint/no-explicit-any async downloadFile(filepath: string): Promise<any> { try { - return await axios.get(await this.downloadFileUrl(filepath)); + return await this.httpClient.get(await this.downloadFileUrl(filepath)); } catch (e) { return ""; } diff --git a/src/views/workflows/ArbitraryWorkflowView.vue b/src/views/workflows/ArbitraryWorkflowView.vue index a30b0ca50e3989dec5b44c79a4b15d4d10c07c2c..cc1c87b0c28732dd69e14989523e1f7c01230f64 100644 --- a/src/views/workflows/ArbitraryWorkflowView.vue +++ b/src/views/workflows/ArbitraryWorkflowView.vue @@ -11,6 +11,7 @@ import { Toast } from "bootstrap"; const props = defineProps<{ repository?: string; commit_hash?: string; + token?: string; }>(); const router = useRouter(); @@ -37,12 +38,13 @@ const workflowExecutionState = reactive<{ const showDocumentation = ref<boolean>(true); let errorToast: Toast | null = null; -function downloadVersionFiles(repository: string, commit_hash: string) { +function downloadVersionFiles( + repository: string, + commit_hash: string, + token?: string, +) { workflowState.loading = true; - const repo = GitRepository.buildRepository(repository, commit_hash); - //const descriptionPromise = repo.downloadFile().then((response) => { - // versionState.descriptionMarkdown = response.data; - //}); + const repo = GitRepository.buildRepository(repository, commit_hash, token); Promise.all( requiredRepositoryFiles.map((file) => repo.downloadFile(file).then((response) => { @@ -78,6 +80,7 @@ function startWorkflow( parameters: parameters, report_output_bucket: report_output_bucket, repository_url: props.repository, + token: props.token, }) .then(() => { router.push({ @@ -100,7 +103,7 @@ function startWorkflow( onMounted(() => { errorToast = new Toast("#arbitraryWorkflowExecutionViewErrorToast"); if (props.commit_hash && props.repository) { - downloadVersionFiles(props.repository, props.commit_hash); + downloadVersionFiles(props.repository, props.commit_hash, props.token); } }); </script> diff --git a/tsconfig.json b/tsconfig.json index 4f9228fddf23ba7b1325ff0a1b6ee7c54790b20e..32d333fdaaf1d55c8b026efa8c95a87163d171f1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "extends": [ - "@tsconfig/node16/tsconfig.json", + "@tsconfig/node18/tsconfig.json", "@vue/tsconfig/tsconfig.json" ], "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],