diff options
Diffstat (limited to 'apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue')
-rw-r--r-- | apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue b/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue new file mode 100644 index 00000000000..d1b57a757d8 --- /dev/null +++ b/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue @@ -0,0 +1,322 @@ +<!-- + - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <NcModal v-if="show" + label-id="form-name" + @close="() => $emit('update:show', false)"> + <div class="modal__content"> + <h2 id="form-name"> + {{ t('settings', 'Advanced deploy options') }} + </h2> + <p class="description" style="text-align: center;"> + {{ configuredDeployOptions === null ? t('settings', 'Edit ExApp deploy options before installation') : t('settings', 'Configured ExApp deploy options. Can be set only during installation') }}. + <a href="https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/AdvancedDeployOptions.html">Learn more</a> + </p> + + <h3 v-if="environmentVariables.length > 0 || (configuredDeployOptions !== null && configuredDeployOptions.environment_variables.length > 0)"> + {{ t('settings', 'Environment variables') }} + </h3> + <template v-if="configuredDeployOptions === null"> + <div v-for="envVar in environmentVariables" + :key="envVar.envName" + class="deploy-option"> + <NcTextField :label="envVar.displayName" :value.sync="deployOptions.environment_variables[envVar.envName]" /> + <p class="description"> + {{ envVar.description }} + </p> + </div> + </template> + <template v-else-if="Object.keys(configuredDeployOptions).length > 0"> + <p class="description"> + {{ t('settings', 'ExApp container environment variables') }} + </p> + <ul class="envs"> + <li v-for="envVar in Object.keys(configuredDeployOptions.environment_variables)" :key="envVar"> + <NcTextField :label="configuredDeployOptions.environment_variables[envVar].displayName ?? envVar" + :value="configuredDeployOptions.environment_variables[envVar].value" + readonly /> + <p class="description"> + {{ configuredDeployOptions.environment_variables[envVar].description }} + </p> + </li> + </ul> + </template> + <template v-else> + <p class="description"> + {{ t('settings', 'No environment variables defined') }} + </p> + </template> + + <h3>{{ t('settings', 'Mounts') }}</h3> + <template v-if="configuredDeployOptions === null"> + <p class="description"> + {{ t('settings', 'Define host folder mounts to bind to the ExApp container') }} + </p> + <p class="warning"> + {{ t('settings', 'Must exist on the Deploy daemon host prior to installing the ExApp') }} + </p> + <div v-for="mount in deployOptions.mounts" + :key="mount.hostPath" + class="deploy-option" + style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;"> + <NcTextField :label="t('settings', 'Host path')" :value.sync="mount.hostPath" /> + <NcTextField :label="t('settings', 'Container path')" :value.sync="mount.containerPath" /> + <NcCheckboxRadioSwitch :checked.sync="mount.readonly"> + {{ t('settings', 'Read-only') }} + </NcCheckboxRadioSwitch> + <NcButton :aria-label="t('settings', 'Remove mount')" + style="margin-top: 6px;" + @click="removeMount(mount)"> + <template #icon> + <NcIconSvgWrapper :path="mdiDelete" /> + </template> + </NcButton> + </div> + <div v-if="addingMount" class="deploy-option"> + <h4> + {{ t('settings', 'New mount') }} + </h4> + <div style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;"> + <NcTextField ref="newMountHostPath" + :label="t('settings', 'Host path')" + :aria-label="t('settings', 'Enter path to host folder')" + :value.sync="newMountPoint.hostPath" /> + <NcTextField :label="t('settings', 'Container path')" + :aria-label="t('settings', 'Enter path to container folder')" + :value.sync="newMountPoint.containerPath" /> + <NcCheckboxRadioSwitch :checked.sync="newMountPoint.readonly" + :aria-label="t('settings', 'Toggle read-only mode')"> + {{ t('settings', 'Read-only') }} + </NcCheckboxRadioSwitch> + </div> + <div style="display: flex; align-items: center; margin-top: 4px;"> + <NcButton :aria-label="t('settings', 'Confirm adding new mount')" + @click="addMountPoint"> + <template #icon> + <NcIconSvgWrapper :path="mdiCheck" /> + </template> + {{ t('settings', 'Confirm') }} + </NcButton> + <NcButton :aria-label="t('settings', 'Cancel adding mount')" + style="margin-left: 4px;" + @click="cancelAddMountPoint"> + <template #icon> + <NcIconSvgWrapper :path="mdiClose" /> + </template> + {{ t('settings', 'Cancel') }} + </NcButton> + </div> + </div> + <NcButton v-if="!addingMount" + :aria-label="t('settings', 'Add mount')" + style="margin-top: 5px;" + @click="() => { + addingMount = true + $nextTick(() => { + this.$refs.newMountHostPath.focus() + }) + }"> + <template #icon> + <NcIconSvgWrapper :path="mdiPlus" /> + </template> + {{ t('settings', 'Add mount') }} + </NcButton> + </template> + <template v-else-if="configuredDeployOptions.mounts.length > 0"> + <p class="description"> + {{ t('settings', 'ExApp container mounts') }} + </p> + <div v-for="mount in configuredDeployOptions.mounts" + :key="mount.hostPath" + class="deploy-option" + style="display: flex; align-items: center; justify-content: space-between; flex-direction: row;"> + <NcTextField :label="t('settings', 'Host path')" :value.sync="mount.hostPath" readonly /> + <NcTextField :label="t('settings', 'Container path')" :value.sync="mount.containerPath" readonly /> + <NcCheckboxRadioSwitch :checked.sync="mount.readonly" disabled> + {{ t('settings', 'Read-only') }} + </NcCheckboxRadioSwitch> + </div> + </template> + <template v-else> + <p class="description"> + {{ t('settings', 'No mounts defined') }} + </p> + </template> + + <NcButton v-if="!app.active && (app.canInstall || app.isCompatible) && configuredDeployOptions === null" + :title="enableButtonTooltip" + :aria-label="enableButtonTooltip" + type="primary" + :disabled="!app.canInstall || installing || isLoading || !defaultDeployDaemonAccessible || isInitializing || isDeploying" + style="margin-top: 10px;" + @click.stop="() => { + enable(app.id, deployOptions) + $emit('update:show', false) + }"> + {{ enableButtonText }} + </NcButton> + </div> + </NcModal> +</template> + +<script> +import { computed, ref } from 'vue' + +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' + +import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' +import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' + +import { mdiPlus, mdiCheck, mdiClose, mdiDelete } from '@mdi/js' + +import { useAppApiStore } from '../../store/app-api-store.ts' +import { useAppsStore } from '../../store/apps-store.ts' + +import AppManagement from '../../mixins/AppManagement.js' + +export default { + name: 'AppDeployOptionsModal', + components: { + NcModal, + NcTextField, + NcButton, + NcCheckboxRadioSwitch, + NcIconSvgWrapper, + }, + mixins: [AppManagement], + props: { + app: { + type: Object, + required: true, + }, + show: { + type: Boolean, + required: true, + }, + }, + setup(props) { + // for AppManagement mixin + const store = useAppsStore() + const appApiStore = useAppApiStore() + + const environmentVariables = computed(() => { + if (props.app?.releases?.length === 1) { + return props.app?.releases[0]?.environmentVariables || [] + } + return [] + }) + + const deployOptions = ref({ + environment_variables: environmentVariables.value.reduce((acc, envVar) => { + acc[envVar.envName] = envVar.default || '' + return acc + }, {}), + mounts: [], + }) + + return { + environmentVariables, + deployOptions, + store, + appApiStore, + mdiPlus, + mdiCheck, + mdiClose, + mdiDelete, + } + }, + data() { + return { + addingMount: false, + newMountPoint: { + hostPath: '', + containerPath: '', + readonly: false, + }, + addingPortBinding: false, + configuredDeployOptions: null, + } + }, + watch: { + show(newShow) { + if (newShow) { + this.fetchExAppDeployOptions() + } else { + this.configuredDeployOptions = null + } + }, + }, + methods: { + addMountPoint() { + this.deployOptions.mounts.push(this.newMountPoint) + this.newMountPoint = { + hostPath: '', + containerPath: '', + readonly: false, + } + this.addingMount = false + }, + cancelAddMountPoint() { + this.newMountPoint = { + hostPath: '', + containerPath: '', + readonly: false, + } + this.addingMount = false + }, + removeMount(mountToRemove) { + this.deployOptions.mounts = this.deployOptions.mounts.filter(mount => mount !== mountToRemove) + }, + async fetchExAppDeployOptions() { + return axios.get(generateUrl(`/apps/app_api/apps/deploy-options/${this.app.id}`)) + .then(response => { + this.configuredDeployOptions = response.data + }) + .catch(() => { + this.configuredDeployOptions = null + }) + }, + }, +} +</script> + +<style scoped> +.modal__content { + margin: 40px; +} + +.modal__content h2 { + text-align: center; +} + +.deploy-option { + margin: calc(var(--default-grid-baseline) * 4) 0; + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.envs { + width: 100%; + overflow: auto; + height: 100%; + max-height: 300px; + + li { + margin: 10px 0; + } +} + +.description { + margin-top: 4px; + font-size: 0.8em; + color: var(--color-text-lighter); +} +</style> |