WIP: address review comments, small fixes

Signed-off-by: Andrey Borysenko <andrey18106x@gmail.com>
pull/48665/head
Andrey Borysenko 2024-10-17 03:38:05 +07:00
parent 3a840c102a
commit e4d586697c
No known key found for this signature in database
GPG Key ID: 934CB29F9F59B0D1
9 changed files with 74 additions and 81 deletions

@ -90,7 +90,12 @@ class AppSettingsController extends Controller {
$this->initialState->provideInitialState('appstoreDeveloperDocs', $this->urlGenerator->linkToDocs('developer-manual'));
$this->initialState->provideInitialState('appstoreUpdateCount', count($this->getAppsWithUpdates()));
$this->provideAppApiState();
if ($this->appManager->isInstalled('app_api')) {
try {
Server::get(\OCA\AppAPI\Service\ExAppsPageService::class)->provideAppApiState($this->initialState);
} catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
}
}
$policy = new ContentSecurityPolicy();
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
@ -104,40 +109,6 @@ class AppSettingsController extends Controller {
return $templateResponse;
}
/**
* @psalm-suppress UndefinedClass
*/
private function provideAppApiState(): void {
$appApiEnabled = $this->appManager->isInstalled('app_api');
$this->initialState->provideInitialState('appApiEnabled', $appApiEnabled);
$daemonConfigAccessible = false;
$defaultDaemonConfig = null;
if ($appApiEnabled) {
$exAppFetcher = Server::get(\OCA\AppAPI\Fetcher\ExAppFetcher::class);
$this->initialState->provideInitialState('appstoreExAppUpdateCount', count($exAppFetcher->getExAppsWithUpdates()));
$defaultDaemonConfigName = $this->config->getAppValue('app_api', 'default_daemon_config');
if ($defaultDaemonConfigName !== '') {
$daemonConfigService = Server::get(\OCA\AppAPI\Service\DaemonConfigService::class);
$daemonConfig = $daemonConfigService->getDaemonConfigByName($defaultDaemonConfigName);
if ($daemonConfig !== null) {
$defaultDaemonConfig = $daemonConfig->jsonSerialize();
unset($defaultDaemonConfig['deploy_config']['haproxy_password']);
$dockerActions = Server::get(\OCA\AppAPI\DeployActions\DockerActions::class);
$dockerActions->initGuzzleClient($daemonConfig);
$daemonConfigAccessible = $dockerActions->ping($dockerActions->buildDockerUrl($daemonConfig));
if (!$daemonConfigAccessible) {
$this->logger->warning(sprintf('Deploy daemon "%s" is not accessible by Nextcloud. Please verify its configuration', $daemonConfig->getName()));
}
}
}
}
$this->initialState->provideInitialState('defaultDaemonConfigAccessible', $daemonConfigAccessible);
$this->initialState->provideInitialState('defaultDaemonConfig', $defaultDaemonConfig);
}
/**
* Get all active entries for the app discover section
*/
@ -475,6 +446,7 @@ class AppSettingsController extends Controller {
$formattedApps[] = [
'id' => $app['id'],
'app_api' => false,
'name' => $app['translations'][$currentLanguage]['name'] ?? $app['translations']['en']['name'],
'description' => $app['translations'][$currentLanguage]['description'] ?? $app['translations']['en']['description'],
'summary' => $app['translations'][$currentLanguage]['summary'] ?? $app['translations']['en']['summary'],

@ -41,6 +41,7 @@ export interface IAppstoreApp {
preview?: string
screenshot?: string
app_api: boolean
active: boolean
internal: boolean
removeable: boolean
@ -48,6 +49,8 @@ export interface IAppstoreApp {
canInstall: boolean
canUninstall: boolean
isCompatible: boolean
needsDownload: boolean
update: string | null
appstoreData: Record<string, never>
releases?: IAppstoreAppRelease[]
@ -75,18 +78,18 @@ export interface IDeployDaemon {
}
export interface IExAppStatus {
action: string,
deploy: number,
deploy_start_time: number,
error: string,
init: number,
init_start_time: number,
type: string,
action: string
deploy: number
deploy_start_time: number
error: string
init: number
init_start_time: number
type: string
}
export interface IAppstoreExApp extends IAppstoreApp {
daemon: IDeployDaemon,
status: IExAppStatus,
error: string,
app_api: boolean,
daemon: IDeployDaemon | null | undefined
status: IExAppStatus | Record<string, never>
error: string
removable: boolean
}

@ -14,8 +14,8 @@
<component :is="dataItemTag"
class="app-image app-image-icon"
:headers="getDataItemHeaders(`app-table-col-icon`)">
<div v-if="(listView && !app.preview && !app?.app_api) || (!listView && !screenshotLoaded && !app?.app_api)" class="icon-settings-dark" />
<NcIconSvgWrapper v-else-if="(listView && app?.app_api && !app.preview) || (!listView && !screenshotLoaded && app?.app_api)"
<div v-if="!app?.app_api && shouldDisplayDefaultIcon" class="icon-settings-dark" />
<NcIconSvgWrapper v-else-if="app?.app_api && shouldDisplayDefaultIcon"
:path="mdiCogOutline()"
:size="listView ? 24 : 48"
style="min-width: auto; min-height: auto; height: 100%;" />
@ -79,6 +79,7 @@
<NcButton v-if="app.update"
type="primary"
:disabled="installing || isLoading || !defaultDeployDaemonAccessible || isManualInstall"
:title="updateButtonText"
@click.stop="update(app.id)">
{{ t('settings', 'Update to {update}', {update:app.update}) }}
</NcButton>
@ -182,6 +183,9 @@ export default {
withSidebar() {
return !!this.$route.params.id
},
shouldDisplayDefaultIcon() {
return this.listView && !this.app.preview || !this.listView && !this.screenshotLoaded
},
},
watch: {
'$route.params.id'(id) {

@ -12,11 +12,11 @@
<NcIconSvgWrapper :path="mdiFileChart" :size="24" />
</template>
<div class="daemon">
<h4>{{ t('app_api', 'Deploy Daemon') }}</h4>
<p><b>{{ t('app_api', 'Type') }}</b>: {{ app?.daemon.accepts_deploy_id }}</p>
<p><b>{{ t('app_api', 'Name') }}</b>: {{ app?.daemon.name }}</p>
<p><b>{{ t('app_api', 'Display Name') }}</b>: {{ app?.daemon.display_name }}</p>
<p><b>{{ t('app_api', 'GPUs support') }}</b>: {{ app?.daemon.deploy_config?.computeDevice?.id !== 'cpu' || 'false' }}</p>
<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>: {{ app?.daemon.deploy_config?.computeDevice?.id !== 'cpu' || 'false' }}</p>
</div>
</NcAppSidebarTab>
</template>

@ -5,7 +5,7 @@
import type { Ref } from 'vue'
import type { IAppstoreApp } from '../app-types.ts'
import { mdiCog } from '@mdi/js'
import { mdiCog, mdiCogOutline } from '@mdi/js'
import { computed, ref, watchEffect } from 'vue'
import AppstoreCategoryIcons from '../constants/AppstoreCategoryIcons.ts'
import logger from '../logger.ts'
@ -23,11 +23,17 @@ export function useAppIcon(app: Ref<IAppstoreApp>) {
* Fallback value if no app icon available
*/
const categoryIcon = computed(() => {
const path = [app.value?.category ?? []].flat()
.map((name) => AppstoreCategoryIcons[name])
.filter((icon) => !!icon)
.at(0)
?? mdiCog
let path: string
if (app.value?.app_api) {
// Use different default icon for ExApps (AppAPI)
path = mdiCogOutline
} else {
path = [app.value?.category ?? []].flat()
.map((name) => AppstoreCategoryIcons[name])
.filter((icon) => !!icon)
.at(0)
?? (!app.value?.app_api ? mdiCog : mdiCogOutline)
}
return path ? `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="${path}" /></svg>` : null
})

@ -42,26 +42,26 @@ export default {
return false
},
updateButtonText() {
if (this.app?.daemon?.accepts_deploy_id === 'manual-install') {
return t('app_api', 'manual-install apps cannot be updated')
if (this.app?.app_api && this.app?.daemon?.accepts_deploy_id === 'manual-install') {
return t('settings', 'manual-install apps cannot be updated')
}
return ''
return t('settings', 'Update to {version}', { version: this.app?.update })
},
enableButtonText() {
if (this.app?.app_api) {
if (this.app && Object.hasOwn(this.app?.status, 'action') && this.app.status.action === 'deploy') {
return t('app_api', '{progress}% Deploying', { progress: this.app.status?.deploy })
if (this.app && this.app?.status?.action && this.app?.status?.action === 'deploy') {
return t('settings', '{progress}% Deploying …', { progress: this.app?.status?.deploy ?? 0 })
}
if (this.app && Object.hasOwn(this.app?.status, 'action') && this.app.status.action === 'init') {
return t('app_api', '{progress}% Initializing', { progress: this.app.status?.init })
if (this.app && this.app?.status?.action && this.app?.status?.action === 'init') {
return t('settings', '{progress}% Initializing', { progress: this.app?.status?.init ?? 0 })
}
if (this.app && Object.hasOwn(this.app?.status, 'action') && this.app.status.action === 'healthcheck') {
return t('app_api', 'Healthchecking')
if (this.app && this.app?.status?.action && this.app?.status?.action === 'healthcheck') {
return t('settings', 'Health checking')
}
if (this.app.needsDownload) {
return t('app_api', 'Deploy and Enable')
return t('settings', 'Deploy and Enable')
}
return t('app_api', 'Enable')
return t('settings', 'Enable')
} else {
if (this.app.needsDownload) {
return t('settings', 'Download and enable')
@ -71,17 +71,17 @@ export default {
},
disableButtonText() {
if (this.app?.app_api) {
if (this.app && Object.hasOwn(this.app?.status, 'action') && this.app.status.action === 'deploy') {
return t('app_api', '{progress}% Deploying', { progress: this.app.status?.deploy })
if (this.app && this.app?.status?.action && this.app?.status?.action === 'deploy') {
return t('settings', '{progress}% Deploying …', { progress: this.app?.status?.deploy })
}
if (this.app && Object.hasOwn(this.app?.status, 'action') && this.app.status.action === 'init') {
return t('app_api', '{progress}% Initializing', { progress: this.app.status?.init })
if (this.app && this.app?.status?.action && this.app?.status?.action === 'init') {
return t('settings', '{progress}% Initializing', { progress: this.app?.status?.init })
}
if (this.app && Object.hasOwn(this.app?.status, 'action') && this.app.status.action === 'healthcheck') {
return t('app_api', 'Healthchecking')
if (this.app && this.app?.status?.action && this.app?.status?.action === 'healthcheck') {
return t('settings', 'Health checking')
}
}
return t('app_api', 'Disable')
return t('settings', 'Disable')
},
forceEnableButtonText() {
if (this.app.needsDownload) {

@ -25,7 +25,7 @@ const state = {
const mutations = {
APPS_API_FAILURE(state, error) {
showError(t('app_api', 'An error occurred during the request. Unable to proceed.') + '<br>' + error.error.response.data.data.message, { isHTML: true })
showError(t('settings', 'An error occurred during the request. Unable to proceed.') + '<br>' + error.error.response.data.data.message, { isHTML: true })
console.error(state, error)
},
@ -227,7 +227,7 @@ const actions = {
.catch(() => {
context.commit('setError', {
appId: [appId],
error: t('app_api', 'Error: This app cannot be enabled because it makes the server unstable'),
error: t('settings', 'Error: This app cannot be enabled because it makes the server unstable'),
})
})
})

@ -16,7 +16,7 @@ const state = {
updateCount: loadState('settings', 'appstoreUpdateCount', 0),
loading: {},
gettingCategoriesPromise: null,
appApiEnabled: loadState('settings', 'appApiEnabled'),
appApiEnabled: loadState('settings', 'appApiEnabled', false),
}
const mutations = {

@ -84,7 +84,15 @@ const { appIcon } = useAppIcon(app)
/**
* The second text line shown on the sidebar
*/
const licenseText = computed(() => app.value ? t('settings', 'Version {version}, {license}-licensed', { version: app.value.version, license: app.value.licence.toString().toUpperCase() }) : '')
const licenseText = computed(() => {
if (!app.value) {
return ''
}
if (app.value.license !== '') {
return t('settings', 'Version {version}, {license}-licensed', { version: app.value.version, license: app.value.licence.toString().toUpperCase() })
}
return t('settings', 'Version {version}', { version: app.value.version })
})
const activeTab = ref('details')
watch([app], () => { activeTab.value = 'details' })