aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/settings/src/app-types.ts1
-rw-r--r--apps/settings/src/components/AppAPI/DaemonEnableSelection.vue84
-rw-r--r--apps/settings/src/components/AppAPI/DaemonSelectionList.vue84
-rw-r--r--apps/settings/src/components/AppAPI/DaemonSelectionModal.vue70
-rw-r--r--apps/settings/src/components/AppList/AppItem.vue26
-rw-r--r--apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue15
-rw-r--r--apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue34
-rw-r--r--apps/settings/src/mixins/AppManagement.js4
-rw-r--r--apps/settings/src/store/app-api-store.ts17
9 files changed, 325 insertions, 10 deletions
diff --git a/apps/settings/src/app-types.ts b/apps/settings/src/app-types.ts
index 49f0d5a1709..0c448ca907c 100644
--- a/apps/settings/src/app-types.ts
+++ b/apps/settings/src/app-types.ts
@@ -75,6 +75,7 @@ export interface IDeployDaemon {
id: number,
name: string,
protocol: string,
+ exAppsCount: number,
}
export interface IExAppStatus {
diff --git a/apps/settings/src/components/AppAPI/DaemonEnableSelection.vue b/apps/settings/src/components/AppAPI/DaemonEnableSelection.vue
new file mode 100644
index 00000000000..e8aac3705d0
--- /dev/null
+++ b/apps/settings/src/components/AppAPI/DaemonEnableSelection.vue
@@ -0,0 +1,84 @@
+<template>
+ <div class="daemon">
+ <NcListItem :name="itemTitle"
+ :details="isDefault ? t('settings', 'Default') : ''"
+ :force-display-actions="true"
+ :counter-number="daemon.exAppsCount"
+ :class="{'daemon-default': isDefault }"
+ counter-type="highlighted"
+ @click.stop="selectDaemonAndInstall">
+ <template #subname>
+ {{ daemon.accepts_deploy_id }}
+ </template>
+ </NcListItem>
+ </div>
+</template>
+
+<script>
+import NcListItem from '@nextcloud/vue/components/NcListItem'
+import AppManagement from '../../mixins/AppManagement.js'
+import { useAppsStore } from '../../store/apps-store'
+import { useAppApiStore } from '../../store/app-api-store'
+
+export default {
+ name: 'DaemonEnableSelection',
+ components: {
+ NcListItem,
+ },
+ mixins: [AppManagement],
+ props: {
+ daemon: {
+ type: Object,
+ required: true,
+ default: () => {},
+ },
+ isDefault: {
+ type: Boolean,
+ required: true,
+ default: () => false,
+ },
+ app: {
+ type: Object,
+ required: true,
+ default: () => {},
+ },
+ deployOptions: {
+ type: Object,
+ required: false,
+ default: () => null,
+ },
+ },
+ setup() {
+ const store = useAppsStore()
+ const appApiStore = useAppApiStore()
+
+ return {
+ store,
+ appApiStore,
+ }
+ },
+ computed: {
+ itemTitle() {
+ return this.daemon.name + ' - ' + this.daemon.display_name
+ },
+ daemons() {
+ return this.appApiStore.dockerDaemons
+ },
+ },
+ methods: {
+ closeModal() {
+ this.$emit('close')
+ },
+ selectDaemonAndInstall() {
+ this.closeModal()
+ this.enable(this.app.id, this.daemon, this.deployOptions)
+ },
+ },
+}
+</script>
+
+<style lang="scss">
+.daemon-default > .list-item {
+ background-color: var(--color-background-dark);
+}
+</style>
diff --git a/apps/settings/src/components/AppAPI/DaemonSelectionList.vue b/apps/settings/src/components/AppAPI/DaemonSelectionList.vue
new file mode 100644
index 00000000000..7c88c1da81e
--- /dev/null
+++ b/apps/settings/src/components/AppAPI/DaemonSelectionList.vue
@@ -0,0 +1,84 @@
+<template>
+ <div class="daemon-selection-list">
+ <ul v-if="dockerDaemons.length > 0"
+ :aria-label="t('settings', 'Registered Deploy daemons list')">
+ <DaemonEnableSelection v-for="daemon in dockerDaemons"
+ :key="daemon.id"
+ :daemon="daemon"
+ :is-default="defaultDaemon.name === daemon.name"
+ :app="app"
+ :deploy-options="deployOptions"
+ @close="closeModal" />
+ </ul>
+ <NcEmptyContent v-else
+ :name="t('settings', 'No Deploy daemons configured')"
+ :description="t('settings', 'Register a custom one or setup from available templates')">
+ <template #icon>
+ <FormatListBullet :size="20" />
+ </template>
+ </NcEmptyContent>
+ </div>
+</template>
+
+<script>
+import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
+import FormatListBullet from 'vue-material-design-icons/FormatListBulleted.vue'
+import DaemonEnableSelection from './DaemonEnableSelection.vue'
+import { useAppApiStore } from '../../store/app-api-store.ts'
+import { useAppsStore } from '../../store/apps-store.ts'
+
+export default {
+ name: 'DaemonSelectionList',
+ components: {
+ FormatListBullet,
+ DaemonEnableSelection,
+ NcEmptyContent,
+ },
+ props: {
+ app: {
+ type: Object,
+ required: true,
+ },
+ deployOptions: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ setup() {
+ const store = useAppsStore()
+ const appApiStore = useAppApiStore()
+
+ return {
+ store,
+ appApiStore,
+ }
+ },
+ computed: {
+ dockerDaemons() {
+ return this.appApiStore.dockerDaemons
+ },
+ defaultDaemon() {
+ return this.appApiStore.defaultDaemon
+ },
+ },
+ methods: {
+ closeModal() {
+ this.$emit('close')
+ },
+ },
+}
+</script>
+
+<style scoped lang="scss">
+.daemon-selection-list {
+ max-height: 300px;
+ overflow-y: scroll;
+ padding: 2rem;
+
+ .empty-content {
+ margin-top: 0;
+ text-align: center;
+ }
+}
+</style>
diff --git a/apps/settings/src/components/AppAPI/DaemonSelectionModal.vue b/apps/settings/src/components/AppAPI/DaemonSelectionModal.vue
new file mode 100644
index 00000000000..2f77cd8a992
--- /dev/null
+++ b/apps/settings/src/components/AppAPI/DaemonSelectionModal.vue
@@ -0,0 +1,70 @@
+<template>
+ <div class="daemon-selection-modal">
+ <NcModal :show="show"
+ :name="t('settings', 'Daemon selection')"
+ size="normal"
+ @close="closeModal">
+ <div class="select-modal-body">
+ <h3>{{ t('settings', 'Choose Deploy Daemon for {appName}', {appName: app.name }) }}</h3>
+ <DaemonSelectionList :app="app"
+ :deploy-options="deployOptions"
+ @close="closeModal" />
+ </div>
+ </NcModal>
+ </div>
+</template>
+
+<script>
+import NcModal from '@nextcloud/vue/components/NcModal'
+import DaemonSelectionList from './DaemonSelectionList.vue'
+import { useAppsStore } from '../../store/apps-store'
+import { useAppApiStore } from '../../store/app-api-store'
+
+export default {
+ name: 'DaemonSelectionModal',
+ components: {
+ NcModal,
+ DaemonSelectionList,
+ },
+ props: {
+ show: {
+ type: Boolean,
+ required: true,
+ default: false,
+ },
+ app: {
+ type: Object,
+ required: true,
+ },
+ deployOptions: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ setup() {
+ const store = useAppsStore()
+ const appApiStore = useAppApiStore()
+
+ return {
+ store,
+ appApiStore,
+ }
+ },
+ data() {
+ return {
+ selectDaemonModal: false,
+ }
+ },
+ methods: {
+ closeModal() {
+ this.$emit('update:show', false)
+ },
+ },
+}
+</script>
+<style scoped>
+.select-modal-body h3 {
+ text-align: center;
+}
+</style>
diff --git a/apps/settings/src/components/AppList/AppItem.vue b/apps/settings/src/components/AppList/AppItem.vue
index d0f39f3c74a..aa995c5b039 100644
--- a/apps/settings/src/components/AppList/AppItem.vue
+++ b/apps/settings/src/components/AppList/AppItem.vue
@@ -100,7 +100,7 @@
:aria-label="enableButtonTooltip"
type="primary"
:disabled="!app.canInstall || installing || isLoading || !defaultDeployDaemonAccessible || isInitializing || isDeploying"
- @click.stop="enable(app.id)">
+ @click.stop="enableButtonAction">
{{ enableButtonText }}
</NcButton>
<NcButton v-else-if="!app.active"
@@ -111,6 +111,10 @@
@click.stop="forceEnable(app.id)">
{{ forceEnableButtonText }}
</NcButton>
+
+ <DaemonSelectionModal v-if="app?.app_api && showSelectDaemonModal"
+ :show.sync="showSelectDaemonModal"
+ :app="app" />
</component>
</component>
</template>
@@ -126,6 +130,7 @@ import NcButton from '@nextcloud/vue/components/NcButton'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import { mdiCogOutline } from '@mdi/js'
import { useAppApiStore } from '../../store/app-api-store.ts'
+import DaemonSelectionModal from '../AppAPI/DaemonSelectionModal.vue'
export default {
name: 'AppItem',
@@ -134,6 +139,7 @@ export default {
AppScore,
NcButton,
NcIconSvgWrapper,
+ DaemonSelectionModal,
},
mixins: [AppManagement, SvgFilterMixin],
props: {
@@ -177,6 +183,7 @@ export default {
isSelected: false,
scrolled: false,
screenshotLoaded: false,
+ showSelectDaemonModal: false,
}
},
computed: {
@@ -219,6 +226,23 @@ export default {
getDataItemHeaders(columnName) {
return this.useBundleView ? [this.headers, columnName].join(' ') : null
},
+ showSelectionModal() {
+ 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>
diff --git a/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue b/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue
index 04c49827b02..e04d8884b96 100644
--- a/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue
+++ b/apps/settings/src/components/AppStoreSidebar/AppDeployOptionsModal.vue
@@ -187,6 +187,10 @@ export default {
type: Boolean,
required: true,
},
+ showDaemonSelectionModal: {
+ type: Function,
+ required: true,
+ },
},
setup(props) {
// for AppManagement mixin
@@ -277,8 +281,15 @@ export default {
this.configuredDeployOptions = null
})
},
- submitDeployOptions() {
- this.enable(this.app.id, this.deployOptions)
+ 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) {
+ this.showDaemonSelectionModal(this.deployOptions)
+ } else {
+ this.enable(this.app.id, this.app.daemon, this.deployOptions)
+ }
this.$emit('update:show', false)
},
},
diff --git a/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue b/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue
index 3aa42f1d15a..55f5c2477e9 100644
--- a/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue
+++ b/apps/settings/src/components/AppStoreSidebar/AppDetailsTab.vue
@@ -68,7 +68,7 @@
type="button"
:value="enableButtonText"
:disabled="!app.canInstall || installing || isLoading || !defaultDeployDaemonAccessible || isInitializing || isDeploying"
- @click="enable(app.id)">
+ @click="enableButtonAction">
<input v-else-if="!app.active && !app.canInstall"
:title="forceEnableButtonTooltip"
:aria-label="forceEnableButtonTooltip"
@@ -194,7 +194,12 @@
<AppDeployOptionsModal v-if="app?.app_api"
:show.sync="showDeployOptionsModal"
- :app="app" />
+ :app="app"
+ :show-daemon-selection-modal="showSelectionModal" />
+ <DaemonSelectionModal v-if="app?.app_api && showSelectDaemonModal"
+ :show.sync="showSelectDaemonModal"
+ :app="app"
+ :deploy-options="deployOptions" />
</div>
</NcAppSidebarTab>
</template>
@@ -207,6 +212,7 @@ 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 DaemonSelectionModal from '../AppAPI/DaemonSelectionModal.vue'
import AppManagement from '../../mixins/AppManagement.js'
import { mdiBug, mdiFeatureSearch, mdiStar, mdiTextBox, mdiTooltipQuestion, mdiToyBrickPlus } from '@mdi/js'
@@ -224,6 +230,7 @@ export default {
NcSelect,
NcCheckboxRadioSwitch,
AppDeployOptionsModal,
+ DaemonSelectionModal,
},
mixins: [AppManagement],
@@ -256,6 +263,8 @@ export default {
groupCheckedAppsData: false,
removeData: false,
showDeployOptionsModal: false,
+ showSelectDaemonModal: false,
+ deployOptions: null,
}
},
@@ -365,6 +374,9 @@ export default {
this.removeData = false
},
},
+ beforeUnmount() {
+ this.deployOptions = null
+ },
mounted() {
if (this.app.groups.length > 0) {
this.groupCheckedAppsData = true
@@ -374,6 +386,24 @@ export default {
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>
diff --git a/apps/settings/src/mixins/AppManagement.js b/apps/settings/src/mixins/AppManagement.js
index b877b8dd88e..bac170fba6b 100644
--- a/apps/settings/src/mixins/AppManagement.js
+++ b/apps/settings/src/mixins/AppManagement.js
@@ -188,9 +188,9 @@ export default {
.catch((error) => { showError(error) })
}
},
- enable(appId, deployOptions = []) {
+ enable(appId, daemon = null, deployOptions = []) {
if (this.app?.app_api) {
- this.appApiStore.enableApp(appId, deployOptions)
+ this.appApiStore.enableApp(appId, daemon, deployOptions)
.then(() => { rebuildNavigation() })
.catch((error) => { showError(error) })
} else {
diff --git a/apps/settings/src/store/app-api-store.ts b/apps/settings/src/store/app-api-store.ts
index f2f950d6948..deb48d5532d 100644
--- a/apps/settings/src/store/app-api-store.ts
+++ b/apps/settings/src/store/app-api-store.ts
@@ -25,6 +25,7 @@ interface AppApiState {
statusUpdater: number | null | undefined
daemonAccessible: boolean
defaultDaemon: IDeployDaemon | null
+ dockerDaemons: IDeployDaemon[]
}
export const useAppApiStore = defineStore('app-api-apps', {
@@ -36,6 +37,7 @@ export const useAppApiStore = defineStore('app-api-apps', {
statusUpdater: null,
daemonAccessible: loadState('settings', 'defaultDaemonConfigAccessible', false),
defaultDaemon: loadState('settings', 'defaultDaemonConfig', null),
+ dockerDaemons: [],
}),
getters: {
@@ -76,12 +78,12 @@ export const useAppApiStore = defineStore('app-api-apps', {
})
},
- enableApp(appId: string, deployOptions: IDeployOptions[] = []) {
+ enableApp(appId: string, daemon: IDeployDaemon, deployOptions: IDeployOptions[] = []) {
this.setLoading(appId, true)
this.setLoading('install', true)
return confirmPassword().then(() => {
- return axios.post(generateUrl(`/apps/app_api/apps/enable/${appId}`), { deployOptions })
+ return axios.post(generateUrl(`/apps/app_api/apps/enable/${appId}/${daemon.name}`), { deployOptions })
.then((response) => {
this.setLoading(appId, false)
this.setLoading('install', false)
@@ -91,7 +93,7 @@ export const useAppApiStore = defineStore('app-api-apps', {
if (!app.installed) {
app.installed = true
app.needsDownload = false
- app.daemon = this.defaultDaemon
+ app.daemon = daemon
app.status = {
type: 'install',
action: 'deploy',
@@ -293,6 +295,15 @@ export const useAppApiStore = defineStore('app-api-apps', {
})
},
+ async fetchDockerDaemons() {
+ return axios.get(generateUrl('/apps/app_api/daemons'))
+ .then((res) => {
+ this.defaultDaemon = res.data.daemons.find((daemon: IDeployDaemon) => daemon.name === res.data.default_daemon_config)
+ this.dockerDaemons = res.data.daemons.filter((daemon: IDeployDaemon) => daemon.accepts_deploy_id === 'docker-install')
+ return res
+ })
+ },
+
updateAppsStatus() {
clearInterval(this.statusUpdater as number)
const initializingOrDeployingApps = this.getInitializingOrDeployingApps