Skip to content

Commit e3c0efa

Browse files
authored
Merge pull request #48988 from nextcloud/backport/48665/stable30
[stable30] feat(settings): migrate AppAPI ExApps management to settings
2 parents 2ebef1e + 0ee0e04 commit e3c0efa

107 files changed

Lines changed: 898 additions & 201 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/settings/lib/Controller/AppSettingsController.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
use OCP\IRequest;
4444
use OCP\IURLGenerator;
4545
use OCP\L10N\IFactory;
46+
use OCP\Server;
4647
use Psr\Log\LoggerInterface;
4748

4849
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
@@ -77,6 +78,8 @@ public function __construct(
7778
}
7879

7980
/**
81+
* @psalm-suppress UndefinedClass AppAPI is shipped since 30.0.1
82+
*
8083
* @return TemplateResponse
8184
*/
8285
#[NoCSRFRequired]
@@ -88,6 +91,13 @@ public function viewApps(): TemplateResponse {
8891
$this->initialState->provideInitialState('appstoreDeveloperDocs', $this->urlGenerator->linkToDocs('developer-manual'));
8992
$this->initialState->provideInitialState('appstoreUpdateCount', count($this->getAppsWithUpdates()));
9093

94+
if ($this->appManager->isInstalled('app_api')) {
95+
try {
96+
Server::get(\OCA\AppAPI\Service\ExAppsPageService::class)->provideAppApiState($this->initialState);
97+
} catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
98+
}
99+
}
100+
91101
$policy = new ContentSecurityPolicy();
92102
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
93103

@@ -431,6 +441,7 @@ private function getAppsForCategory($requestedCategory = ''): array {
431441

432442
$formattedApps[] = [
433443
'id' => $app['id'],
444+
'app_api' => false,
434445
'name' => $app['translations'][$currentLanguage]['name'] ?? $app['translations']['en']['name'],
435446
'description' => $app['translations'][$currentLanguage]['description'] ?? $app['translations']['en']['description'],
436447
'summary' => $app['translations'][$currentLanguage]['summary'] ?? $app['translations']['en']['summary'],

apps/settings/src/app-types.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,54 @@ export interface IAppstoreApp {
4141
preview?: string
4242
screenshot?: string
4343

44+
app_api: boolean
4445
active: boolean
4546
internal: boolean
46-
removeable: boolean
47+
removable: boolean
4748
installed: boolean
4849
canInstall: boolean
49-
canUninstall: boolean
50+
canUnInstall: boolean
5051
isCompatible: boolean
52+
needsDownload: boolean
53+
update?: string
5154

5255
appstoreData: Record<string, never>
5356
releases?: IAppstoreAppRelease[]
5457
}
58+
59+
export interface IComputeDevice {
60+
id: string,
61+
label: string,
62+
}
63+
64+
export interface IDeployConfig {
65+
computeDevice: IComputeDevice,
66+
net: string,
67+
nextcloud_url: string,
68+
}
69+
70+
export interface IDeployDaemon {
71+
accepts_deploy_id: string,
72+
deploy_config: IDeployConfig,
73+
display_name: string,
74+
host: string,
75+
id: number,
76+
name: string,
77+
protocol: string,
78+
}
79+
80+
export interface IExAppStatus {
81+
action: string
82+
deploy: number
83+
deploy_start_time: number
84+
error: string
85+
init: number
86+
init_start_time: number
87+
type: string
88+
}
89+
90+
export interface IAppstoreExApp extends IAppstoreApp {
91+
daemon: IDeployDaemon | null | undefined
92+
status: IExAppStatus | Record<string, never>
93+
error: string
94+
}

apps/settings/src/components/AppList.vue

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,10 @@
140140

141141
<script>
142142
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
143-
import { useAppsStore } from '../store/apps-store'
144143
import AppItem from './AppList/AppItem.vue'
145144
import pLimit from 'p-limit'
146145
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
147-
import AppManagement from '../mixins/AppManagement'
146+
import { useAppApiStore } from '../store/app-api-store'
148147
149148
export default {
150149
name: 'AppList',
@@ -153,8 +152,6 @@ export default {
153152
NcButton,
154153
},
155154
156-
mixins: [AppManagement],
157-
158155
props: {
159156
category: {
160157
type: String,
@@ -163,9 +160,9 @@ export default {
163160
},
164161
165162
setup() {
166-
const store = useAppsStore()
163+
const appApiStore = useAppApiStore()
167164
return {
168-
store,
165+
appApiStore,
169166
}
170167
},
171168
@@ -179,7 +176,10 @@ export default {
179176
return this.apps.filter(app => app.update).length
180177
},
181178
loading() {
182-
return this.$store.getters.loading('list')
179+
if (!this.$store.getters['appApiApps/isAppApiEnabled']) {
180+
return this.$store.getters.loading('list')
181+
}
182+
return this.$store.getters.loading('list') || this.appApiStore.getLoading('list')
183183
},
184184
hasPendingUpdate() {
185185
return this.apps.filter(app => app.update).length > 0
@@ -188,7 +188,9 @@ export default {
188188
return this.hasPendingUpdate && this.useListView
189189
},
190190
apps() {
191-
const apps = this.$store.getters.getAllApps
191+
// Exclude ExApps from the list if AppAPI is disabled
192+
const exApps = this.$store.getters.isAppApiEnabled ? this.appApiStore.getAllApps : []
193+
const apps = [...this.$store.getters.getAllApps, ...exApps]
192194
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
193195
.sort(function(a, b) {
194196
const sortStringA = '' + (a.active ? 0 : 1) + (a.update ? 0 : 1) + a.name
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
<template>
6+
<span v-if="daemon"
7+
class="app-daemon-badge"
8+
:title="daemon.name">
9+
<NcIconSvgWrapper :path="mdiFileChart" :size="20" inline />
10+
{{ daemon.display_name }}
11+
</span>
12+
</template>
13+
14+
<script setup lang="ts">
15+
import type { IDeployDaemon } from '../../app-types.ts'
16+
import { mdiFileChart } from '@mdi/js'
17+
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
18+
19+
defineProps<{
20+
daemon?: IDeployDaemon
21+
}>()
22+
</script>
23+
24+
<style scoped lang="scss">
25+
.app-daemon-badge {
26+
color: var(--color-text-maxcontrast);
27+
background-color: transparent;
28+
border: 1px solid var(--color-text-maxcontrast);
29+
border-radius: var(--border-radius);
30+
31+
display: flex;
32+
flex-direction: row;
33+
gap: 6px;
34+
padding: 3px 6px;
35+
width: fit-content;
36+
}
37+
</style>

apps/settings/src/components/AppList/AppItem.vue

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
<component :is="dataItemTag"
1515
class="app-image app-image-icon"
1616
:headers="getDataItemHeaders(`app-table-col-icon`)">
17-
<div v-if="(listView && !app.preview) || (!listView && !screenshotLoaded)" class="icon-settings-dark" />
17+
<div v-if="!app?.app_api && shouldDisplayDefaultIcon" class="icon-settings-dark" />
18+
<NcIconSvgWrapper v-else-if="app.app_api && shouldDisplayDefaultIcon"
19+
:path="mdiCogOutline"
20+
:size="listView ? 24 : 48"
21+
style="min-width: auto; min-height: auto; height: 100%;" />
1822

19-
<svg v-else-if="listView && app.preview"
23+
<svg v-else-if="listView && app.preview && !app.app_api"
2024
width="32"
2125
height="32"
2226
viewBox="0 0 32 32">
@@ -71,10 +75,11 @@
7175
<div v-if="app.error" class="warning">
7276
{{ app.error }}
7377
</div>
74-
<div v-if="isLoading" class="icon icon-loading-small" />
78+
<div v-if="isLoading || isInitializing" class="icon icon-loading-small" />
7579
<NcButton v-if="app.update"
7680
type="primary"
77-
:disabled="installing || isLoading"
81+
:disabled="installing || isLoading || !defaultDeployDaemonAccessible || isManualInstall"
82+
:title="updateButtonText"
7883
@click.stop="update(app.id)">
7984
{{ t('settings', 'Update to {update}', {update:app.update}) }}
8085
</NcButton>
@@ -86,23 +91,23 @@
8691
{{ t('settings', 'Remove') }}
8792
</NcButton>
8893
<NcButton v-if="app.active"
89-
:disabled="installing || isLoading"
94+
:disabled="installing || isLoading || isInitializing || isDeploying"
9095
@click.stop="disable(app.id)">
91-
{{ t('settings','Disable') }}
96+
{{ disableButtonText }}
9297
</NcButton>
9398
<NcButton v-if="!app.active && (app.canInstall || app.isCompatible)"
9499
:title="enableButtonTooltip"
95100
:aria-label="enableButtonTooltip"
96101
type="primary"
97-
:disabled="!app.canInstall || installing || isLoading"
102+
:disabled="!app.canInstall || installing || isLoading || !defaultDeployDaemonAccessible || isInitializing || isDeploying"
98103
@click.stop="enable(app.id)">
99104
{{ enableButtonText }}
100105
</NcButton>
101106
<NcButton v-else-if="!app.active"
102107
:title="forceEnableButtonTooltip"
103108
:aria-label="forceEnableButtonTooltip"
104109
type="secondary"
105-
:disabled="installing || isLoading"
110+
:disabled="installing || isLoading || !defaultDeployDaemonAccessible"
106111
@click.stop="forceEnable(app.id)">
107112
{{ forceEnableButtonText }}
108113
</NcButton>
@@ -118,13 +123,17 @@ import AppLevelBadge from './AppLevelBadge.vue'
118123
import AppManagement from '../../mixins/AppManagement.js'
119124
import SvgFilterMixin from '../SvgFilterMixin.vue'
120125
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
126+
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
127+
import { mdiCogOutline } from '@mdi/js'
128+
import { useAppApiStore } from '../../store/app-api-store.ts'
121129
122130
export default {
123131
name: 'AppItem',
124132
components: {
125133
AppLevelBadge,
126134
AppScore,
127135
NcButton,
136+
NcIconSvgWrapper,
128137
},
129138
mixins: [AppManagement, SvgFilterMixin],
130139
props: {
@@ -155,7 +164,13 @@ export default {
155164
},
156165
setup() {
157166
const store = useAppsStore()
158-
return { store }
167+
const appApiStore = useAppApiStore()
168+
169+
return {
170+
store,
171+
appApiStore,
172+
mdiCogOutline,
173+
}
159174
},
160175
data() {
161176
return {
@@ -174,6 +189,9 @@ export default {
174189
withSidebar() {
175190
return !!this.$route.params.id
176191
},
192+
shouldDisplayDefaultIcon() {
193+
return (this.listView && !this.app.preview) || (!this.listView && !this.screenshotLoaded)
194+
},
177195
},
178196
watch: {
179197
'$route.params.id'(id) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<template>
7+
<NcAppSidebarTab v-if="app?.daemon"
8+
id="daemon"
9+
:name="t('settings', 'Daemon')"
10+
:order="3">
11+
<template #icon>
12+
<NcIconSvgWrapper :path="mdiFileChart" :size="24" />
13+
</template>
14+
<div class="daemon">
15+
<h4>{{ t('settings', 'Deploy Daemon') }}</h4>
16+
<p><b>{{ t('settings', 'Type') }}</b>: {{ app?.daemon.accepts_deploy_id }}</p>
17+
<p><b>{{ t('settings', 'Name') }}</b>: {{ app?.daemon.name }}</p>
18+
<p><b>{{ t('settings', 'Display Name') }}</b>: {{ app?.daemon.display_name }}</p>
19+
<p><b>{{ t('settings', 'GPUs support') }}</b>: {{ gpuSupport }}</p>
20+
<p><b>{{ t('settings', 'Compute device') }}</b>: {{ app?.daemon?.deploy_config?.computeDevice?.label }}</p>
21+
</div>
22+
</NcAppSidebarTab>
23+
</template>
24+
25+
<script setup lang="ts">
26+
import type { IAppstoreExApp } from '../../app-types'
27+
28+
import NcAppSidebarTab from '@nextcloud/vue/dist/Components/NcAppSidebarTab.js'
29+
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
30+
31+
import { mdiFileChart } from '@mdi/js'
32+
import { ref } from 'vue'
33+
34+
const props = defineProps<{
35+
app: IAppstoreExApp,
36+
}>()
37+
38+
const gpuSupport = ref(props.app?.daemon?.deploy_config?.computeDevice?.id !== 'cpu' || false)
39+
</script>
40+
41+
<style scoped lang="scss">
42+
.daemon {
43+
padding: 20px;
44+
45+
h4 {
46+
font-weight: bold;
47+
margin: 10px auto;
48+
}
49+
}
50+
</style>

0 commit comments

Comments
 (0)