Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExApps management and Admin settings UI logic fixes #177

Merged
merged 5 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions lib/Controller/ExAppsPageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -380,14 +380,14 @@ private function buildLocalAppsList(array $apps, array $exApps): array {
'level' => 100,
'missingMaxOwnCloudVersion' => false,
'missingMinOwnCloudVersion' => false,
'canInstall' => true,
'canUnInstall' => false,
'canInstall' => true, // to allow "remove" command for manual-install
'canUnInstall' => !($exApp->getEnabled() === 1),
'isCompatible' => true,
'screenshot' => '',
'score' => 0,
'ratingNumOverall' => 0,
'ratingNumThresholdReached' => false,
'removable' => false,
'removable' => true, // to allow "remove" command for manual-install
'active' => $exApp->getEnabled() === 1,
'needsDownload' => false,
'groups' => [],
Expand Down Expand Up @@ -468,15 +468,16 @@ public function enableApps(array $appIds, array $groups = []): JSONResponse {
], Http::STATUS_INTERNAL_SERVER_ERROR);
}

//if (!$this->service->enableExApp($exApp)) {
// return new JSONResponse(['data' => ['message' => $this->l10n->t('Failed to enable ExApp')]], Http::STATUS_INTERNAL_SERVER_ERROR);
//}
$scopes = $this->exAppApiScopeService->mapScopeGroupsToNames(array_map(function (ExAppScope $exAppScope) {
return $exAppScope->getScopeGroup();
}, $this->exAppScopeService->getExAppScopes($exApp)));
return new JSONResponse([
'data' => [
'daemon_config' => $daemonConfig,
'systemApp' => $this->exAppUsersService->exAppUserExists($exApp->getAppid(), ''),
'exAppUrl' => AppAPIService::getExAppUrl($exApp->getProtocol(), $exApp->getHost(), $exApp->getPort()),
'status' => json_decode($exApp->getStatus(), true),
'scopes' => $scopes,
]
]);
}
Expand Down Expand Up @@ -662,12 +663,16 @@ public function updateApp(string $appId): JSONResponse {
}
}

$scopes = $this->exAppApiScopeService->mapScopeGroupsToNames(array_map(function (ExAppScope $exAppScope) {
return $exAppScope->getScopeGroup();
}, $this->exAppScopeService->getExAppScopes($exApp)));
return new JSONResponse([
'data' => [
'appid' => $appId,
'status' => ['progress' => 0],
'systemApp' => filter_var($exAppInfo['system_app'], FILTER_VALIDATE_BOOLEAN),
'exAppUrl' => AppAPIService::getExAppUrl($exAppInfo['protocol'], $exAppInfo['host'], (int) $exAppInfo['port']),
'scopes' => $scopes,
]
]);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/DeployActions/DockerActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public function buildDeployParams(DaemonConfig $daemonConfig, SimpleXMLElement $
$secret = $oldEnvs['APP_SECRET'];
$storage = $oldEnvs['APP_PERSISTENT_STORAGE'];
// Preserve previous device requests (GPU)
$deviceRequests = $containerInfo['HostConfig']['DeviceRequests'];
$deviceRequests = $containerInfo['HostConfig']['DeviceRequests'] ?? [];
} else {
$port = $this->service->getExAppRandomPort();
if (isset($deployConfig['gpu']) && filter_var($deployConfig['gpu'], FILTER_VALIDATE_BOOLEAN)) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Apps/AppDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<input v-if="app.active"
class="enable"
type="button"
:value="t('settings','Disable')"
:value="disableButtonText"
:disabled="installing || isLoading || !defaultDeployDaemonAccessible || isInitializing"
@click="disable(app.id)">
<input v-if="!app.active && (app.canInstall || app.isCompatible)"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Apps/AppItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
<NcButton v-if="app.active"
:disabled="installing || isLoading || !defaultDeployDaemonAccessible || isInitializing"
@click.stop="disable(app.id)">
{{ t('settings','Disable') }}
{{ disableButtonText }}
</NcButton>
<NcButton v-if="!app.active && (app.canInstall || app.isCompatible)"
:title="enableButtonTooltip"
Expand Down
59 changes: 48 additions & 11 deletions src/components/DaemonConfig/DaemonConfig.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</template>
{{ !isDefault ? t('app_api', 'Set as default') : t('app_api', 'Default') }}
</NcActionButton>
<NcActionButton icon="icon-delete" @click="deleteDaemonConfig(daemon)">
<NcActionButton icon="icon-delete" :close-after-click="true" @click="deleteDaemonConfig(daemon)">
{{ t('app_api', 'Delete') }}
<template #icon>
<NcLoadingIcon v-if="deleting" :size="20" />
Expand All @@ -31,6 +31,35 @@
:show.sync="showDetailsModal"
:daemon="daemon"
:is-default="isDefault" />
<NcDialog
v-show="showDeleteDialog"
style="padding: 20px;"
:open.sync="showDeleteDialog"
:content-classes="'confirm-delete-dialog'"
:name="t('app_api', 'Confirm deletion')">
<template #actions>
<NcDialogButton :label="t('app_api', 'Cancel')" :callback="() => showDeleteDialog = false">
<template #icon>
<Cancel :size="20" />
</template>
</NcDialogButton>
<NcDialogButton :label="t('app_api', 'Ok')" :callback="() => _deleteDaemonConfig(daemon)">
<template #icon>
<Check :size="20" />
</template>
</NcDialogButton>
</template>
<template #default>
<div class="confirm-delete-dialog">
<p>{{ t('app_api', 'Are you sure you want delete Deploy Daemon?') }}</p>
<NcNoteCard>
<template #default>
{{ t('app_api', 'This action will not remove installed ExApps on this daemon') }}
</template>
</NcNoteCard>
</div>
</template>
</NcDialog>
</div>
</template>

Expand All @@ -39,6 +68,11 @@ import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'
import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
import NcDialogButton from '@nextcloud/vue/dist/Components/NcDialogButton.js'
import Cancel from 'vue-material-design-icons/Cancel.vue'
import Check from 'vue-material-design-icons/Check.vue'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'

Expand All @@ -53,6 +87,11 @@ export default {
NcActionButton,
NcLoadingIcon,
CheckBold,
NcDialog,
NcDialogButton,
Cancel,
Check,
NcNoteCard,
DaemonConfigDetailsModal,
},
props: {
Expand Down Expand Up @@ -80,6 +119,7 @@ export default {
showDetailsModal: false,
settingDefault: false,
deleting: false,
showDeleteDialog: false,
}
},
computed: {
Expand Down Expand Up @@ -109,16 +149,7 @@ export default {
})
},
deleteDaemonConfig(daemon) {
const self = this
OC.dialogs.confirm(
t('app_api', 'Are you sure you want delete Deploy Daemon?'),
t('app_api', 'Confirm Deploy daemon deletion'),
function(success) {
if (success) {
self._deleteDaemonConfig(daemon)
}
},
)
this.showDeleteDialog = true
},
_deleteDaemonConfig(daemon) {
this.deleting = true
Expand All @@ -128,10 +159,12 @@ export default {
this.getAllDaemons()
}
this.deleting = false
this.showDetailsModal = false
})
.catch(err => {
console.debug(err)
this.deleting = false
this.showDetailsModal = false
})
},
},
Expand All @@ -143,4 +176,8 @@ export default {
background-color: var(--color-background-dark);
border-radius: var(--border-radius-pill);
}

.confirm-delete-dialog {
padding: 20px;
}
</style>
31 changes: 24 additions & 7 deletions src/components/DaemonConfig/RegisterDaemonConfigModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
:placeholder="t('app_api', 'Set daemon as default')"
:aria-label="t('app_api', 'Set daemon as default')"
style="margin-top: 1rem;">
{{ t('app_api', 'Default daemon') }}
{{ t('app_api', 'Set as default daemon') }}
</NcCheckboxRadioSwitch>
<template v-if="acceptsDeployId !== 'manual-install'">
<NcButton :aria-label="t('app_api', 'Deploy config')" style="margin: 10px 0;" @click="deployConfigSettingsOpened = !deployConfigSettingsOpened">
Expand All @@ -87,17 +87,27 @@
<NcInputField
id="deploy-config-net"
:value.sync="deployConfig.net"
:placeholder="t('app_api', 'Docker network name (default: host)')"
:aria-label="t('app_api', 'Docker network name (default: host)')"
:helper-text="t('app_api', 'Docker network name (default: host)')" />
:placeholder="t('app_api', 'Docker network name')"
:aria-label="t('app_api', 'Docker network name')"
:helper-text="t('app_api', 'Docker network name')" />
</div>
<div class="external-label">
<label for="deploy-config-host">{{ t('app_api', 'Host') }}</label>
<NcInputField
v-if="deployConfig.net === 'host'"
id="deploy-config-host"
:value.sync="deployConfig.host"
:placeholder="t('app_api', 'Hostname to reach ExApp (optional)')"
:aria-label="t('app_api', 'Hostname to reach ExApp (optional)')"
:helper-text="t('app_api', 'Hostname to reach ExApp (optional)')" />
</div>
<NcCheckboxRadioSwitch
id="deploy-config-gpus"
:checked.sync="deployConfig.gpu"
:placeholder="t('app_api', 'Enable gpus support (attach gpu to ExApp containers)')"
:aria-label="t('app_api', 'Enable gpus support (attach gpu to ExApp containers))')"
style="margin-top: 1rem;">
{{ t('app_api', 'GPUs support') }}
{{ t('app_api', 'Enable GPUs support') }}
</NcCheckboxRadioSwitch>
<p v-if="deployConfig.gpu" class="hint">
{{ t('app_api', 'All GPU devices will be requested to be enabled in ExApp containers') }}
Expand Down Expand Up @@ -173,7 +183,7 @@ export default {
deployConfigSettingsOpened: false,
deployConfig: {
net: 'host',
host: '',
host: 'localhost',
ssl_key: '',
ssl_key_password: '',
ssl_cert: '',
Expand Down Expand Up @@ -205,6 +215,13 @@ export default {
this.displayName = 'Docker Local'
}
},
'deployConfig.net'(newNet) {
if (newNet === 'host') {
this.deployConfig.host = 'localhost'
} else {
this.deployConfig.host = ''
}
},
},
methods: {
registerDaemon() {
Expand Down Expand Up @@ -269,7 +286,7 @@ export default {
this.deployConfigSettingsOpened = false
this.deployConfig = {
net: 'host',
host: '',
host: 'localhost',
ssl_key: '',
ssl_key_password: '',
ssl_cert: '',
Expand Down
10 changes: 5 additions & 5 deletions src/constants/AppsConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { translate as t } from '@nextcloud/l10n'

/** Enum of verification constants, according to Apps */
export const APPS_SECTION_ENUM = Object.freeze({
enabled: t('settings', 'Active apps'),
disabled: t('settings', 'Disabled apps'),
updates: t('settings', 'Updates'),
featured: t('settings', 'Featured apps'),
supported: t('settings', 'Supported apps'), // From subscription
enabled: t('app_api', 'Active apps'),
disabled: t('app_api', 'Disabled apps'),
updates: t('app_api', 'Updates'),
featured: t('app_api', 'Featured apps'),
supported: t('app_api', 'Supported apps'), // From subscription
})
14 changes: 13 additions & 1 deletion src/mixins/AppManagement.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export default {
}
return t('app_api', 'Enable')
},
disableButtonText() {
if (this.app && Object.hasOwn(this.app?.status, 'progress')) {
return t('app_api', '{progress}% Initializing', { progress: this.app.status?.progress })
}
return t('app_api', 'Disable')
},
forceEnableButtonText() {
if (this.app.needsDownload) {
return t('app_api', 'Allow untested app')
Expand All @@ -44,7 +50,13 @@ export default {
return base
},
defaultDeployDaemonAccessible() {
return this.$store.getters.getDaemonAccessible || false
if (this.app?.daemon && this.app?.daemon?.accepts_deploy_id === 'manual-install') {
return true
}
if (this.app?.daemon?.accepts_deploy_id === 'docker-install') {
return this.$store.getters.getDaemonAccessible === true
}
return this.$store.getters.getDaemonAccessible
},
},

Expand Down
19 changes: 16 additions & 3 deletions src/store/apps.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const mutations = {
app.error = null
},

enableApp(state, { appId, groups, daemon, systemApp, exAppUrl, status }) {
enableApp(state, { appId, groups, daemon, systemApp, exAppUrl, status, scopes }) {
const app = state.apps.find(app => app.id === appId)
if (daemon) {
app.installed = true
Expand All @@ -76,11 +76,18 @@ const mutations = {
app.exAppUrl = exAppUrl
}
if (status) {
app.status = status
app.status = {
progress: 0,
}
}
if (scopes) {
app.scopes = scopes
}
app.active = true
app.groups = groups
app.canUnInstall = false
app.removable = true
app.error = null
},

disableApp(state, appId) {
Expand All @@ -100,20 +107,24 @@ const mutations = {
state.apps.find(app => app.id === appId).canUnInstall = false
state.apps.find(app => app.id === appId).canInstall = true
state.apps.find(app => app.id === appId).daemon = null
state.apps.find(app => app.id === appId).status = {}
state.apps.find(app => app.id === appId).scopes = null
if (state.apps.find(app => app.id === appId).update !== null) {
state.updateCount--
}
state.apps.find(app => app.id === appId).update = null
},

updateApp(state, { appId, systemApp, exAppUrl, status }) {
updateApp(state, { appId, systemApp, exAppUrl, status, scopes }) {
const app = state.apps.find(app => app.id === appId)
const version = app.update
app.update = null
app.version = version
app.systemApp = systemApp
app.exAppUrl = exAppUrl
app.status = status
app.scopes = scopes
app.error = null
state.updateCount--
},

Expand Down Expand Up @@ -220,6 +231,7 @@ const actions = {
systemApp: response.data.data?.systemApp,
exAppUrl: response.data.data?.exAppUrl,
status: response.data.data?.status,
scopes: response.data.data?.scopes,
})
})

Expand Down Expand Up @@ -341,6 +353,7 @@ const actions = {
systemApp: response.data.data?.systemApp,
exAppUrl: response.data.data?.exAppUrl,
status: response.data.data?.status,
scopes: response.data.data?.scopes,
})
context.dispatch('updateAppsStatus')
return true
Expand Down
Loading