diff options
Diffstat (limited to 'apps/settings/src/components/AppStoreSidebar')
5 files changed, 482 insertions, 86 deletions
diff --git a/apps/settings/src/components/AppStoreSidebar/AppDeployDaemonTab.vue b/apps/settings/src/components/AppStoreSidebar/AppDeployDaemonTab.vue new file mode 100644 index 00000000000..7c0b8ea4421 --- /dev/null +++ b/apps/settings/src/components/AppStoreSidebar/AppDeployDaemonTab.vue @@ -0,0 +1,50 @@ +<!-- + - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <NcAppSidebarTab v-if="app?.daemon" + id="daemon" + :name="t('settings', 'Daemon')" + :order="3"> + <template #icon> + <NcIconSvgWrapper :path="mdiFileChart" :size="24" /> + </template> + <div class="daemon"> + <h4>{{ t('settings', 'Deploy Daemon') }}</h4> + <p><b>{{ t('settings', 'Type') }}</b>: {{ app?.daemon.accepts_deploy_id }}</p> + <p><b>{{ t('settings', 'Name') }}</b>: {{ app?.daemon.name }}</p> + <p><b>{{ t('settings', 'Display Name') }}</b>: {{ app?.daemon.display_name }}</p> + <p><b>{{ t('settings', 'GPUs support') }}</b>: {{ gpuSupport }}</p> + <p><b>{{ t('settings', 'Compute device') }}</b>: {{ app?.daemon?.deploy_config?.computeDevice?.label }}</p> + </div> + </NcAppSidebarTab> +</template> + +<script setup lang="ts"> +import type { IAppstoreExApp } from '../../app-types' + +import NcAppSidebarTab from '@nextcloud/vue/components/NcAppSidebarTab' +import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' + +import { mdiFileChart } from '@mdi/js' +import { ref } from 'vue' + +const props = defineProps<{ + app: IAppstoreExApp, +}>() + +const gpuSupport = ref(props.app?.daemon?.deploy_config?.computeDevice?.id !== 'cpu' || false) +</script> + +<style scoped lang="scss"> +.daemon { + padding: 20px; + + h4 { + font-weight: bold; + margin: 10px auto; + } +} +</style> diff --git a/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue b/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue new file mode 100644 index 00000000000..0544c3848be --- /dev/null +++ b/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue @@ -0,0 +1,320 @@ +<!-- + - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <NcDialog :open="show" + size="normal" + :name="t('settings', 'Advanced deploy options')" + @update:open="$emit('update:show', $event)"> + <div class="modal__content"> + <p class="deploy-option__hint"> + {{ configuredDeployOptions === null ? t('settings', 'Edit ExApp deploy options before installation') : t('settings', 'Configured ExApp deploy options. Can be set only during installation') }}. + <a v-if="deployOptionsDocsUrl" :href="deployOptionsDocsUrl"> + {{ t('settings', '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="deploy-option__hint"> + {{ envVar.description }} + </p> + </div> + </template> + <fieldset v-else-if="Object.keys(configuredDeployOptions).length > 0" + class="envs"> + <legend class="deploy-option__hint"> + {{ t('settings', 'ExApp container environment variables') }} + </legend> + <NcTextField v-for="(value, key) in configuredDeployOptions.environment_variables" + :key="key" + :label="value.displayName ?? key" + :helper-text="value.description" + :value="value.value" + readonly /> + </fieldset> + <template v-else> + <p class="deploy-option__hint"> + {{ t('settings', 'No environment variables defined') }} + </p> + </template> + + <h3>{{ t('settings', 'Mounts') }}</h3> + <template v-if="configuredDeployOptions === null"> + <p class="deploy-option__hint"> + {{ t('settings', 'Define host folder mounts to bind to the ExApp container') }} + </p> + <NcNoteCard type="info" :text="t('settings', 'Must exist on the Deploy daemon host prior to installing the ExApp')" /> + <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="mdiDeleteOutline" /> + </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="startAddingMount"> + <template #icon> + <NcIconSvgWrapper :path="mdiPlus" /> + </template> + {{ t('settings', 'Add mount') }} + </NcButton> + </template> + <template v-else-if="configuredDeployOptions.mounts.length > 0"> + <p class="deploy-option__hint"> + {{ 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> + <p v-else class="deploy-option__hint"> + {{ t('settings', 'No mounts defined') }} + </p> + </div> + + <template v-if="!app.active && (app.canInstall || app.isCompatible) && configuredDeployOptions === null" #actions> + <NcButton :title="enableButtonTooltip" + :aria-label="enableButtonTooltip" + type="primary" + :disabled="!app.canInstall || installing || isLoading || !defaultDeployDaemonAccessible || isInitializing || isDeploying" + @click.stop="submitDeployOptions"> + {{ enableButtonText }} + </NcButton> + </template> + </NcDialog> +</template> + +<script> +import { computed, ref } from 'vue' + +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +import { loadState } from '@nextcloud/initial-state' +import { emit } from '@nextcloud/event-bus' + +import NcDialog from '@nextcloud/vue/components/NcDialog' +import NcTextField from '@nextcloud/vue/components/NcTextField' +import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' + +import { mdiPlus, mdiCheck, mdiClose, mdiDeleteOutline } 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: { + NcDialog, + NcTextField, + NcButton, + NcNoteCard, + 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, + mdiDeleteOutline, + } + }, + data() { + return { + addingMount: false, + newMountPoint: { + hostPath: '', + containerPath: '', + readonly: false, + }, + addingPortBinding: false, + configuredDeployOptions: null, + deployOptionsDocsUrl: loadState('settings', 'deployOptionsDocsUrl', null), + } + }, + watch: { + show(newShow) { + if (newShow) { + this.fetchExAppDeployOptions() + } else { + this.configuredDeployOptions = null + } + }, + }, + methods: { + startAddingMount() { + this.addingMount = true + this.$nextTick(() => { + this.$refs.newMountHostPath.focus() + }) + }, + 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 + }) + }, + async submitDeployOptions() { + await this.appApiStore.fetchDockerDaemons() + if (this.appApiStore.dockerDaemons.length === 1 && this.app.needsDownload) { + this.enable(this.app.id, this.appApiStore.dockerDaemons[0], this.deployOptions) + } else if (this.app.needsDownload) { + emit('showDaemonSelectionModal', this.deployOptions) + } else { + this.enable(this.app.id, this.app.daemon, this.deployOptions) + } + this.$emit('update:show', false) + }, + }, +} +</script> + +<style scoped> +.deploy-option { + margin: calc(var(--default-grid-baseline) * 4) 0; + display: flex; + flex-direction: column; + align-items: flex-start; + + &__hint { + margin-top: 4px; + font-size: 0.8em; + color: var(--color-text-maxcontrast); + } +} + +.envs { + width: 100%; + overflow: auto; + height: 100%; + max-height: 300px; + + li { + margin: 10px 0; + } +} +</style> diff --git a/apps/settings/src/components/AppStoreSidebar/AppDescriptionTab.vue b/apps/settings/src/components/AppStoreSidebar/AppDescriptionTab.vue index 0466ddf4f4c..299d084ef9e 100644 --- a/apps/settings/src/components/AppStoreSidebar/AppDescriptionTab.vue +++ b/apps/settings/src/components/AppStoreSidebar/AppDescriptionTab.vue @@ -1,24 +1,7 @@ <!-- - - @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> - - - - @author Julius Härtl <jus@bitgrid.net> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - - --> + - SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <NcAppSidebarTab id="desc" @@ -39,8 +22,8 @@ import type { IAppstoreApp } from '../../app-types' import { mdiTextShort } from '@mdi/js' import { translate as t } from '@nextcloud/l10n' -import NcAppSidebarTab from '@nextcloud/vue/dist/Components/NcAppSidebarTab.js' -import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' +import NcAppSidebarTab from '@nextcloud/vue/components/NcAppSidebarTab' +import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' import Markdown from '../Markdown.vue' defineProps<{ diff --git a/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue b/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue index 24387581cdd..eb66d8f3e3a 100644 --- a/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue +++ b/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue @@ -1,31 +1,14 @@ <!-- - - @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> - - - - @author Julius Härtl <jus@bitgrid.net> - - - - @license GNU AGPL version 3 or any later version - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - - --> + - SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <NcAppSidebarTab id="details" :name="t('settings', 'Details')" :order="1"> <template #icon> - <NcIconSvgWrapper :path="mdiTextBox" /> + <NcIconSvgWrapper :path="mdiTextBoxOutline" /> </template> <div class="app-details"> <div class="app-details__actions"> @@ -64,19 +47,19 @@ class="update primary" type="button" :value="t('settings', 'Update to {version}', { version: app.update })" - :disabled="installing || isLoading" + :disabled="installing || isLoading || isManualInstall" @click="update(app.id)"> <input v-if="app.canUnInstall" class="uninstall" type="button" :value="t('settings', 'Remove')" :disabled="installing || isLoading" - @click="remove(app.id)"> + @click="remove(app.id, removeData)"> <input v-if="app.active" class="enable" type="button" - :value="t('settings','Disable')" - :disabled="installing || isLoading" + :value="disableButtonText" + :disabled="installing || isLoading || isInitializing || isDeploying" @click="disable(app.id)"> <input v-if="!app.active && (app.canInstall || app.isCompatible)" :title="enableButtonTooltip" @@ -84,8 +67,8 @@ class="enable primary" type="button" :value="enableButtonText" - :disabled="!app.canInstall || installing || isLoading" - @click="enable(app.id)"> + :disabled="!app.canInstall || installing || isLoading || !defaultDeployDaemonAccessible || isInitializing || isDeploying" + @click="enableButtonAction"> <input v-else-if="!app.active && !app.canInstall" :title="forceEnableButtonTooltip" :aria-label="forceEnableButtonTooltip" @@ -94,7 +77,25 @@ :value="forceEnableButtonText" :disabled="installing || isLoading" @click="forceEnable(app.id)"> + <NcButton v-if="app?.app_api && (app.canInstall || app.isCompatible)" + :aria-label="t('settings', 'Advanced deploy options')" + type="secondary" + @click="() => showDeployOptionsModal = true"> + <template #icon> + <NcIconSvgWrapper :path="mdiToyBrickPlusOutline" /> + </template> + {{ t('settings', 'Deploy options') }} + </NcButton> </div> + <p v-if="!defaultDeployDaemonAccessible" class="warning"> + {{ t('settings', 'Default Deploy daemon is not accessible') }} + </p> + <NcCheckboxRadioSwitch v-if="app.canUnInstall" + :checked="removeData" + :disabled="installing || isLoading || !defaultDeployDaemonAccessible" + @update:checked="toggleRemoveData"> + {{ t('settings', 'Delete data on remove') }} + </NcCheckboxRadioSwitch> </div> <ul class="app-details__dependencies"> @@ -114,7 +115,7 @@ </li> </ul> - <div v-if="lastModified" class="app-details__section"> + <div v-if="lastModified && !app.shipped" class="app-details__section"> <h4> {{ t('settings', 'Latest updated') }} </h4> @@ -161,7 +162,7 @@ :aria-label="t('settings', 'Report a bug')" :title="t('settings', 'Report a bug')"> <template #icon> - <NcIconSvgWrapper :path="mdiBug" /> + <NcIconSvgWrapper :path="mdiBugOutline" /> </template> </NcButton> <NcButton :disabled="!app.bugs" @@ -169,7 +170,7 @@ :aria-label="t('settings', 'Request feature')" :title="t('settings', 'Request feature')"> <template #icon> - <NcIconSvgWrapper :path="mdiFeatureSearch" /> + <NcIconSvgWrapper :path="mdiFeatureSearchOutline" /> </template> </NcButton> <NcButton v-if="app.appstoreData?.discussion" @@ -177,7 +178,7 @@ :aria-label="t('settings', 'Ask questions or discuss')" :title="t('settings', 'Ask questions or discuss')"> <template #icon> - <NcIconSvgWrapper :path="mdiTooltipQuestion" /> + <NcIconSvgWrapper :path="mdiTooltipQuestionOutline" /> </template> </NcButton> <NcButton v-if="!app.internal" @@ -190,20 +191,33 @@ </NcButton> </div> </div> + + <AppDeployOptionsModal v-if="app?.app_api" + :show.sync="showDeployOptionsModal" + :app="app" /> + <DaemonSelectionDialog v-if="app?.app_api" + :show.sync="showSelectDaemonModal" + :app="app" + :deploy-options="deployOptions" /> </div> </NcAppSidebarTab> </template> <script> -import NcAppSidebarTab from '@nextcloud/vue/dist/Components/NcAppSidebarTab.js' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcDateTime from '@nextcloud/vue/dist/Components/NcDateTime.js' -import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' -import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' +import { subscribe, unsubscribe } from '@nextcloud/event-bus' +import NcAppSidebarTab from '@nextcloud/vue/components/NcAppSidebarTab' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcDateTime from '@nextcloud/vue/components/NcDateTime' +import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' +import NcSelect from '@nextcloud/vue/components/NcSelect' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' +import AppDeployOptionsModal from './AppDeployOptionsModal.vue' +import DaemonSelectionDialog from '../AppAPI/DaemonSelectionDialog.vue' import AppManagement from '../../mixins/AppManagement.js' -import { mdiBug, mdiFeatureSearch, mdiStar, mdiTextBox, mdiTooltipQuestion } from '@mdi/js' +import { mdiBugOutline, mdiFeatureSearchOutline, mdiStar, mdiTextBoxOutline, mdiTooltipQuestionOutline, mdiToyBrickPlusOutline } from '@mdi/js' import { useAppsStore } from '../../store/apps-store' +import { useAppApiStore } from '../../store/app-api-store' export default { name: 'AppDetailsTab', @@ -214,6 +228,9 @@ export default { NcDateTime, NcIconSvgWrapper, NcSelect, + NcCheckboxRadioSwitch, + AppDeployOptionsModal, + DaemonSelectionDialog, }, mixins: [AppManagement], @@ -226,21 +243,28 @@ export default { setup() { const store = useAppsStore() + const appApiStore = useAppApiStore() return { store, + appApiStore, - mdiBug, - mdiFeatureSearch, + mdiBugOutline, + mdiFeatureSearchOutline, mdiStar, - mdiTextBox, - mdiTooltipQuestion, + mdiTextBoxOutline, + mdiTooltipQuestionOutline, + mdiToyBrickPlusOutline, } }, data() { return { groupCheckedAppsData: false, + removeData: false, + showDeployOptionsModal: false, + showSelectDaemonModal: false, + deployOptions: null, } }, @@ -345,10 +369,45 @@ export default { .sort((a, b) => a.name.localeCompare(b.name)) }, }, + watch: { + 'app.id'() { + this.removeData = false + }, + }, + beforeUnmount() { + this.deployOptions = null + unsubscribe('showDaemonSelectionModal') + }, mounted() { if (this.app.groups.length > 0) { this.groupCheckedAppsData = true } + subscribe('showDaemonSelectionModal', (deployOptions) => { + this.showSelectionModal(deployOptions) + }) + }, + methods: { + toggleRemoveData() { + this.removeData = !this.removeData + }, + showSelectionModal(deployOptions = null) { + this.deployOptions = deployOptions + this.showSelectDaemonModal = true + }, + async enableButtonAction() { + if (!this.app?.app_api) { + this.enable(this.app.id) + return + } + await this.appApiStore.fetchDockerDaemons() + if (this.appApiStore.dockerDaemons.length === 1 && this.app.needsDownload) { + this.enable(this.app.id, this.appApiStore.dockerDaemons[0]) + } else if (this.app.needsDownload) { + this.showSelectionModal() + } else { + this.enable(this.app.id, this.app.daemon) + } + }, }, } </script> @@ -362,6 +421,7 @@ export default { &-manage { // if too many, shrink them and ellipsis display: flex; + align-items: center; input { flex: 0 1 auto; min-width: 0; @@ -419,6 +479,7 @@ export default { border-color: var(--color-error); background: var(--color-main-background); } + .force:hover, .force:active { color: var(--color-main-background); diff --git a/apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue b/apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue index 13e6ecff28f..e65df0341db 100644 --- a/apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue +++ b/apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue @@ -1,25 +1,7 @@ <!-- - - @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> - - - - @author Julius Härtl <jus@bitgrid.net> - - @author Ferdinand Thiessen <opensource@fthiessen.de> - - - - @license AGPL-3.0-or-later - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU Affero General Public License as - - published by the Free Software Foundation, either version 3 of the - - License, or (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU Affero General Public License for more details. - - - - You should have received a copy of the GNU Affero General Public License - - along with this program. If not, see <http://www.gnu.org/licenses/>. - - - --> + - SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <NcAppSidebarTab v-if="hasChangelog" id="changelog" @@ -43,8 +25,8 @@ import { mdiClockFast } from '@mdi/js' import { getLanguage, translate as t } from '@nextcloud/l10n' import { computed } from 'vue' -import NcAppSidebarTab from '@nextcloud/vue/dist/Components/NcAppSidebarTab.js' -import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' +import NcAppSidebarTab from '@nextcloud/vue/components/NcAppSidebarTab' +import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' import Markdown from '../Markdown.vue' // eslint-disable-next-line @typescript-eslint/no-unused-vars |