@conduction/nextcloud-vue 2.56.0-beta.25 → 2.56.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +6 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -2
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCredentials/CnCredentials.vue2.js +11 -10
- package/dist/esm/components/CnCredentials/CnCredentials.vue2.js.map +1 -1
- package/dist/esm/components/CnCredentials/CnCredentials.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +8 -5
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +144 -6
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +12 -11
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +90 -14
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +15 -3
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +26 -0
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +26 -9
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +19 -3
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +153 -38
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +74 -56
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/composables/useObjectLock.js +41 -10
- package/dist/esm/composables/useObjectLock.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +7 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +8 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +88 -1
- package/dist/esm/store/useObjectStore.js +37 -9
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/objectSignals.js +44 -0
- package/dist/esm/utils/objectSignals.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +871 -328
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +88 -1
- package/l10n/en.json +7 -1
- package/l10n/nl.json +8 -2
- package/package.json +1 -1
- package/src/components/CnCardGrid/CnCardGrid.md +1 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +7 -0
- package/src/components/CnCredentials/CnCredentials.vue +17 -16
- package/src/components/CnDetailPage/CnDetailPage.vue +8 -5
- package/src/components/CnIndexPage/CnIndexPage.md +15 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +155 -16
- package/src/components/CnIndexPage/selfModeActions.js +89 -13
- package/src/components/CnIndexPage/useSelfFetchList.js +15 -3
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +89 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +19 -3
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +181 -38
- package/src/composables/__tests__/useObjectLock.endpoints.spec.js +83 -0
- package/src/composables/useObjectLock.js +41 -10
- package/src/css/look-board-index.css +35 -1
- package/src/index.js +1 -0
- package/src/store/useObjectStore.js +37 -9
- package/src/utils/index.js +1 -0
- package/src/utils/objectSignals.js +41 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnCredentials.vue3.js","sources":["../../../../src/components/CnCredentials/CnCredentials.vue?vue&type=template&id=5de04c1a&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnCredentials — the settings surface for the OpenRegister credential\n - broker. Apps occasionally need to act on your behalf against an external\n - service (GitHub, GitLab, …). Rather than hand the secret to the app, you\n - give it once to OpenRegister, which stores it in Keepiq — Nextcloud's\n - native, encrypted credential vault. Apps then make the outbound call\n - THROUGH OpenRegister and never see the secret itself. You decide which\n - apps may use each credential, so one credential can be shared across apps\n - or kept dedicated to one.\n -\n - Two scopes render from this one component:\n - • scope=\"personal\" (default) — the signed-in user's own\n - credentials, shown in an app's *personal* settings. From here you may\n - only add/remove *the app you are currently in* to/from a credential,\n - or add a new credential (the current app is auto-added). You never\n - manage the full app list of another app from here.\n - • scope=\"organisation\" — organisation-wide credentials, shown in an\n - app's *admin* settings. An admin manages the full allowed-app list.\n -\n - Talks to OpenRegister's credential endpoints (metadata only — a secret is\n - write-only and is NEVER returned or displayed):\n - GET /apps/openregister/api/credentials?scope={scope}\n - GET /apps/openregister/api/credentials/providers\n - POST /apps/openregister/api/credentials { name, provider, allowedApps?, secret?, scope? }\n - PUT /apps/openregister/api/credentials/{id} { name?, allowedApps?, secret? }\n - DELETE /apps/openregister/api/credentials/{id}\n -\n - Provider presentation (title, colour, how-to text + link, secret label) is\n - a hardcoded catalogue in this file (PROVIDER_META): new providers get a\n - richer card here without a server change; unknown providers fall back to a\n - neutral tile keyed off the identifier.\n -\n - Renders inside CnAppRoot's `#user-settings` (personal) or an admin surface\n - (organisation). Fails soft: a 404 (broker not installed) degrades to an\n - empty state with a friendly note rather than crashing.\n -->\n<template>\n\t<div class=\"cn-credentials\" :class=\"`cn-credentials--${scope}`\">\n\t\t<p class=\"cn-credentials__intro\">\n\t\t\t{{ introText }}\n\t\t\t<a v-if=\"resolvedVaultUrl\"\n\t\t\t\t:href=\"resolvedVaultUrl\"\n\t\t\t\tclass=\"cn-credentials__vault-link\"\n\t\t\t\ttarget=\"_self\">\n\t\t\t\t{{ t('nextcloud-vue', 'Learn about Keepiq') }} ↗\n\t\t\t</a>\n\t\t</p>\n\n\t\t<!-- What this app uses (the app's manifest credentials[] requirements) -->\n\t\t<section v-if=\"scope === 'personal'\" class=\"cn-credentials__section cn-credentials__supports\">\n\t\t\t<h4 class=\"cn-credentials__section-title\">\n\t\t\t\t{{ t('nextcloud-vue', 'What {app} uses', { app: appDisplayName }) }}\n\t\t\t</h4>\n\t\t\t<p v-if=\"appCredentials.length === 0\" class=\"cn-credentials__muted\">\n\t\t\t\t{{ t('nextcloud-vue', 'This app does not use any external credentials.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-credentials__requests\">\n\t\t\t\t<li v-for=\"(req, index) in appCredentials\"\n\t\t\t\t\t:key=\"`req-${index}`\"\n\t\t\t\t\tclass=\"cn-credentials__request\">\n\t\t\t\t\t<span class=\"cn-credentials__tile-dot\" :style=\"dotStyle(req.provider)\">{{ providerInitial(req.provider) }}</span>\n\t\t\t\t\t<span class=\"cn-credentials__request-body\">\n\t\t\t\t\t\t<span class=\"cn-credentials__request-provider\">{{ providerTitle(req.provider) }}</span>\n\t\t\t\t\t\t<span v-if=\"req.reason\" class=\"cn-credentials__request-reason\">{{ req.reason }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</section>\n\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t<template v-else>\n\t\t\t<NcNoteCard v-if=\"error\"\n\t\t\t\ttype=\"warning\"\n\t\t\t\tclass=\"cn-credentials__error\">\n\t\t\t\t{{ t('nextcloud-vue', 'Credentials are provided by OpenRegister and could not be loaded here.') }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Your / the organisation's credentials -->\n\t\t\t<section class=\"cn-credentials__section\">\n\t\t\t\t<h4 class=\"cn-credentials__section-title\">\n\t\t\t\t\t{{ scope === 'organisation' ? t('nextcloud-vue', 'Organisation credentials') : t('nextcloud-vue', 'Your credentials') }}\n\t\t\t\t</h4>\n\t\t\t\t<p v-if=\"visibleCredentials.length === 0\" class=\"cn-credentials__muted\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'No credentials stored yet.') }}\n\t\t\t\t</p>\n\t\t\t\t<ul v-else class=\"cn-credentials__list\">\n\t\t\t\t\t<li v-for=\"cred in visibleCredentials\"\n\t\t\t\t\t\t:key=\"cred.id\"\n\t\t\t\t\t\tclass=\"cn-credentials__item\">\n\t\t\t\t\t\t<div class=\"cn-credentials__item-head\">\n\t\t\t\t\t\t\t<span class=\"cn-credentials__tile-dot\" :style=\"dotStyle(cred.provider)\">{{ providerInitial(cred.provider) }}</span>\n\t\t\t\t\t\t\t<div class=\"cn-credentials__item-meta\">\n\t\t\t\t\t\t\t\t<span class=\"cn-credentials__item-name\">{{ cred.name }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-credentials__item-provider\">{{ providerTitle(cred.provider) }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class=\"cn-credentials__item-actions\">\n\t\t\t\t\t\t\t\t<template v-if=\"cred.confirmingDelete\">\n\t\t\t\t\t\t\t\t\t<NcButton variant=\"error\"\n\t\t\t\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t\t\t\t@click=\"onDelete(cred)\">\n\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Confirm delete') }}\n\t\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t\t\t\t@click=\"cred.confirmingDelete = false\">\n\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t<NcButton v-else\n\t\t\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t\t\t@click=\"cred.confirmingDelete = true\">\n\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Delete') }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<!-- Personal: only toggle THIS app in/out of the credential. -->\n\t\t\t\t\t\t<div v-if=\"scope === 'personal'\" class=\"cn-credentials__item-toggle\">\n\t\t\t\t\t\t\t<NcCheckboxRadioSwitch :modelValue=\"appAllowed(cred)\"\n\t\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t\t\t\t@update:modelValue=\"toggleThisApp(cred, $event)\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', '{app} may use this credential', { app: appDisplayName }) }}\n\t\t\t\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t\t\t\t<p v-if=\"otherApps(cred).length\" class=\"cn-credentials__also muted\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Also allowed for: {apps}', { apps: otherApps(cred).join(', ') }) }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<!-- Organisation: admin manages the full allowed-app list. -->\n\t\t\t\t\t\t<NcSelect v-else\n\t\t\t\t\t\t\tclass=\"cn-credentials__item-apps\"\n\t\t\t\t\t\t\t:options=\"appOptions\"\n\t\t\t\t\t\t\t:modelValue=\"cred.allowedApps\"\n\t\t\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t\t\t:taggable=\"true\"\n\t\t\t\t\t\t\tkeepOpen\n\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Allowed apps')\"\n\t\t\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'No app may use this credential yet')\"\n\t\t\t\t\t\t\t@update:modelValue=\"onAllowedAppsChange(cred, $event)\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<!-- Add credential — inline wizard: pick a provider, then fill it in. -->\n\t\t\t<section class=\"cn-credentials__section\">\n\t\t\t\t<NcButton v-if=\"!adding\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t@click=\"startAdd\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ t('nextcloud-vue', 'Add credential') }}\n\t\t\t\t</NcButton>\n\n\t\t\t\t<div v-else class=\"cn-credentials__wizard\">\n\t\t\t\t\t<!-- Step 1: provider icon grid -->\n\t\t\t\t\t<template v-if=\"wizardStep === 'provider'\">\n\t\t\t\t\t\t<h4 class=\"cn-credentials__section-title\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Which credential do you want to add?') }}\n\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t<div class=\"cn-credentials__grid\">\n\t\t\t\t\t\t\t<button v-for=\"p in providerGrid\"\n\t\t\t\t\t\t\t\t:key=\"p.identifier\"\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"cn-credentials__grid-tile\"\n\t\t\t\t\t\t\t\t@click=\"pickProvider(p.identifier)\">\n\t\t\t\t\t\t\t\t<span class=\"cn-credentials__tile-dot cn-credentials__tile-dot--lg\" :style=\"dotStyle(p.identifier)\">{{ providerInitial(p.identifier) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-credentials__grid-title\">{{ p.title }}</span>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-credentials__wizard-actions\">\n\t\t\t\t\t\t\t<NcButton variant=\"tertiary\" @click=\"cancelAdd\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<!-- Step 2: provider-specific form + how-to -->\n\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t<div class=\"cn-credentials__wizard-head\">\n\t\t\t\t\t\t\t<NcButton variant=\"tertiary\" @click=\"wizardStep = 'provider'\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Back') }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t<span class=\"cn-credentials__tile-dot\" :style=\"dotStyle(form.provider)\">{{ providerInitial(form.provider) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-credentials__wizard-title\">{{ providerTitle(form.provider) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<NcNoteCard v-if=\"activeMeta.setupHelp\" type=\"info\" class=\"cn-credentials__howto\">\n\t\t\t\t\t\t\t{{ activeMeta.setupHelp }}\n\t\t\t\t\t\t\t<a v-if=\"activeMeta.setupUrl\"\n\t\t\t\t\t\t\t\t:href=\"activeMeta.setupUrl\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclass=\"cn-credentials__howto-link\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open {provider}', { provider: providerTitle(form.provider) }) }} ↗\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</NcNoteCard>\n\n\t\t\t\t\t\t<form class=\"cn-credentials__form\" @submit.prevent=\"onCreate\">\n\t\t\t\t\t\t\t<NcTextField v-model=\"form.name\"\n\t\t\t\t\t\t\t\t:label=\"t('nextcloud-vue', 'Name')\"\n\t\t\t\t\t\t\t\t:helperText=\"t('nextcloud-vue', 'A label to recognise this credential later.')\"\n\t\t\t\t\t\t\t\t:disabled=\"saving\"\n\t\t\t\t\t\t\t\trequired />\n\n\t\t\t\t\t\t\t<NcTextField v-model=\"form.secret\"\n\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t:label=\"activeMeta.secretLabel || t('nextcloud-vue', 'Secret')\"\n\t\t\t\t\t\t\t\t:helperText=\"t('nextcloud-vue', 'Sent to OpenRegister and stored in Keepiq. It is never shown again.')\"\n\t\t\t\t\t\t\t\t:disabled=\"saving\"\n\t\t\t\t\t\t\t\tautocomplete=\"new-password\"\n\t\t\t\t\t\t\t\trequired />\n\n\t\t\t\t\t\t\t<!-- Organisation add: admin also chooses the allowed apps up-front. -->\n\t\t\t\t\t\t\t<NcSelect v-if=\"scope === 'organisation'\"\n\t\t\t\t\t\t\t\t:options=\"appOptions\"\n\t\t\t\t\t\t\t\t:modelValue=\"form.allowedApps\"\n\t\t\t\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t\t\t\t:taggable=\"true\"\n\t\t\t\t\t\t\t\tkeepOpen\n\t\t\t\t\t\t\t\t:disabled=\"saving\"\n\t\t\t\t\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Allowed apps')\"\n\t\t\t\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Choose which apps may use it')\"\n\t\t\t\t\t\t\t\t@update:modelValue=\"form.allowedApps = normaliseApps($event)\" />\n\t\t\t\t\t\t\t<p v-else class=\"cn-credentials__muted cn-credentials__addnote\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', '{app} will be allowed to use this credential. You can change that afterwards.', { app: appDisplayName }) }}\n\t\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t\t<div class=\"cn-credentials__form-actions\">\n\t\t\t\t\t\t\t\t<NcButton variant=\"tertiary\" :disabled=\"saving\" @click=\"cancelAdd\">\n\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\t\t\t:disabled=\"!canSubmit\">\n\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Add credential') }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</form>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\n\nconst CREDENTIALS_PATH = '/apps/openregister/api/credentials'\nconst PROVIDERS_PATH = '/apps/openregister/api/credentials/providers'\n\n/**\n * Hardcoded per-provider presentation. Keyed by the OpenRegister catalogue\n * identifier. `colour` drives the tile; `setupHelp`/`setupUrl` explain how to\n * obtain the secret; `secretLabel` names the field. Providers not listed here\n * still work — they render a neutral tile from their identifier.\n */\nconst PROVIDER_META = {\n\tgithub: {\n\t\ttitle: 'GitHub',\n\t\tcolour: '#1f2328',\n\t\tsetupUrl: 'https://github.com/settings/personal-access-tokens',\n\t\t// No permission level here on purpose. This text is shared by every\n\t\t// consuming app, and the level each one needs is already on screen:\n\t\t// the requests list above renders each app's own `reason`. Asserting\n\t\t// \"read-only\" contradicted Buildiq two blocks higher, which asks for\n\t\t// Administration and Contents at read AND write to publish an app, so a\n\t\t// reader who followed this line made a token that cannot publish.\n\t\tsetupHelp: 'Create a fine-grained personal access token scoped to the repositories or organisation this app should reach, granting the permissions listed above, then paste it below.',\n\t\tsecretLabel: 'Personal access token',\n\t},\n\tgitlab: {\n\t\ttitle: 'GitLab',\n\t\tcolour: '#fc6d26',\n\t\tsetupUrl: 'https://gitlab.com/-/user_settings/personal_access_tokens',\n\t\tsetupHelp: 'Create a personal access token with the read_api (and read_repository) scopes, then paste it below.',\n\t\tsecretLabel: 'Personal access token',\n\t},\n\tdoffin: {\n\t\ttitle: 'Doffin (Norway)',\n\t\tcolour: '#00509e',\n\t\tsetupUrl: 'https://dfo.no/anskaffelser/doffin',\n\t\tsetupHelp: 'Request an APIM subscription key for the Doffin public procurement API, then paste it below.',\n\t\tsecretLabel: 'Subscription key',\n\t},\n}\n\nexport default {\n\tname: 'CnCredentials',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t\tNcSelect,\n\t\tNcTextField,\n\t\tChevronLeft,\n\t\tPlus,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The consuming Nextcloud app id (e.g. \"openbuild-spectr\"). In personal\n\t\t * scope this is the only app you can toggle in/out of a credential.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * A friendly app name for copy (\"{app} may use this credential\"). Falls\n\t\t * back to the appId.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The current app's manifest `credentials[]` declarations — the\n\t\t * providers this app can reach through the broker. Shape:\n\t\t * `[{ provider, reason, scopes }]`. Rendered read-only under the intro.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tappCredentials: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Which credential set to manage: `\"personal\"` (the signed-in user's\n\t\t * own; app-scoped toggle only) or `\"organisation\"` (org-wide; full\n\t\t * allowed-app management, admin surface).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'personal',\n\t\t\tvalidator: (v) => ['personal', 'organisation'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Optional link target explaining the Keepiq vault. Defaults to the\n\t\t * Keepiq app route; pass '' to hide the link.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tvaultUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: false,\n\t\t\tsaving: false,\n\t\t\tcredentials: [],\n\t\t\tproviders: [],\n\t\t\tadding: false,\n\t\t\twizardStep: 'provider',\n\t\t\tform: {\n\t\t\t\tname: '',\n\t\t\t\tprovider: null,\n\t\t\t\tsecret: '',\n\t\t\t\tallowedApps: [],\n\t\t\t},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Friendly current-app label.\n\t\t *\n\t\t * @return {string} App display name.\n\t\t */\n\t\tappDisplayName() {\n\t\t\treturn this.appName || this.appId || t('nextcloud-vue', 'This app')\n\t\t},\n\n\t\t/**\n\t\t * Reframed intro copy per scope.\n\t\t *\n\t\t * @return {string} Intro text.\n\t\t */\n\t\tintroText() {\n\t\t\tif (this.scope === 'organisation') {\n\t\t\t\treturn t('nextcloud-vue', 'Organisation credentials let apps act on behalf of your organisation. The secret is stored in Keepiq — Nextcloud\\'s native credential vault — never in the app. You choose which apps may use each credential.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Apps sometimes need to act on your behalf against an external service. So they never hold your secrets, you give a secret to Nextcloud once and it is kept in Keepiq — a native, encrypted credential vault. Apps then make the call through Keepiq and never see the secret. You decide which apps may use each credential — share one across apps, or keep one per app.')\n\t\t},\n\n\t\t/**\n\t\t * Resolved Keepiq link (explicit prop, or the app route by default).\n\t\t *\n\t\t * @return {string} URL or ''.\n\t\t */\n\t\tresolvedVaultUrl() {\n\t\t\tif (this.vaultUrl === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.vaultUrl || generateUrl('/apps/doriath')\n\t\t},\n\n\t\t/**\n\t\t * The provider tiles offered in the add-wizard grid: what the server\n\t\t * actually offers when known (so we never offer a provider the broker\n\t\t * can't honour), else the full hardcoded set.\n\t\t *\n\t\t * @return {Array<{identifier: string, title: string}>} Grid tiles.\n\t\t */\n\t\tproviderGrid() {\n\t\t\tconst serverIds = this.providers.map((p) => p.identifier)\n\t\t\tlet ids = serverIds.length ? serverIds : Object.keys(PROVIDER_META)\n\t\t\t// When the app declares which providers it uses (appCredentials),\n\t\t\t// only offer those — an app should not let you add a credential for\n\t\t\t// a provider it has no code path to use.\n\t\t\tif (this.supportedProviders.size > 0) {\n\t\t\t\tids = ids.filter((id) => this.supportedProviders.has(id))\n\t\t\t}\n\t\t\treturn ids.map((id) => ({ identifier: id, title: this.providerTitle(id) }))\n\t\t},\n\n\t\t/**\n\t\t * Provider identifiers the app declares it uses, derived from\n\t\t * `appCredentials`. Empty when the app declares nothing — filtering is\n\t\t * then a no-op, so consumers that don't declare providers are unchanged.\n\t\t *\n\t\t * @return {Set<string>} Supported provider identifiers.\n\t\t */\n\t\tsupportedProviders() {\n\t\t\treturn new Set((this.appCredentials || [])\n\t\t\t\t.map((r) => r && r.provider)\n\t\t\t\t.filter(Boolean))\n\t\t},\n\n\t\t/**\n\t\t * Stored credentials to display — filtered to the app's supported\n\t\t * providers when it declares any, so you can only see and authorise\n\t\t * credentials the app can actually use; otherwise all of them.\n\t\t *\n\t\t * @return {Array<object>} Visible credentials.\n\t\t */\n\t\tvisibleCredentials() {\n\t\t\tif (this.supportedProviders.size === 0) {\n\t\t\t\treturn this.credentials\n\t\t\t}\n\t\t\treturn this.credentials.filter((c) => this.supportedProviders.has(c.provider))\n\t\t},\n\n\t\t/**\n\t\t * The union of app ids the organisation allowed-apps picker offers.\n\t\t *\n\t\t * @return {Array<string>} Distinct app ids.\n\t\t */\n\t\tappOptions() {\n\t\t\tconst ids = new Set()\n\t\t\tif (this.appId) {\n\t\t\t\tids.add(this.appId)\n\t\t\t}\n\t\t\tfor (const cred of this.credentials) {\n\t\t\t\tfor (const appId of (cred.allowedApps || [])) {\n\t\t\t\t\tif (appId) {\n\t\t\t\t\t\tids.add(appId)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Array.from(ids)\n\t\t},\n\n\t\t/**\n\t\t * Presentation metadata for the provider currently being added.\n\t\t *\n\t\t * @return {object} Provider meta (may be a neutral fallback).\n\t\t */\n\t\tactiveMeta() {\n\t\t\treturn PROVIDER_META[this.form.provider] || {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the add form can be submitted — name, provider and a secret\n\t\t * are required (the whole point is to store a secret).\n\t\t *\n\t\t * @return {boolean} True when submittable.\n\t\t */\n\t\tcanSubmit() {\n\t\t\treturn !this.saving\n\t\t\t\t&& this.form.name.trim() !== ''\n\t\t\t\t&& !!this.form.provider\n\t\t\t\t&& this.form.secret.trim() !== ''\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Load credentials (scoped) and the provider catalogue in parallel.\n\t\t * Fails soft on a rejected request.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst [credsRes, provRes] = await Promise.all([\n\t\t\t\t\taxios.get(generateUrl(CREDENTIALS_PATH), { params: { scope: this.scope } }),\n\t\t\t\t\taxios.get(generateUrl(PROVIDERS_PATH)),\n\t\t\t\t])\n\t\t\t\tthis.credentials = this.mapCredentials(credsRes?.data?.results)\n\t\t\t\tthis.providers = Array.isArray(provRes?.data?.results) ? provRes.data.results : []\n\t\t\t} catch {\n\t\t\t\tthis.error = true\n\t\t\t\tthis.credentials = []\n\t\t\t\tthis.providers = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Normalise API credential objects into display rows with local state.\n\t\t *\n\t\t * @param {Array<object>} results Raw credential objects.\n\t\t * @return {Array<object>} Display rows.\n\t\t */\n\t\tmapCredentials(results) {\n\t\t\tif (!Array.isArray(results)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.map((c) => ({\n\t\t\t\tid: c.id,\n\t\t\t\tname: c.name || '',\n\t\t\t\tprovider: c.provider || '',\n\t\t\t\tallowedApps: Array.isArray(c.allowedApps) ? c.allowedApps.slice() : [],\n\t\t\t\tsaving: false,\n\t\t\t\tconfirmingDelete: false,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Open the add-credential wizard at the provider grid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartAdd() {\n\t\t\tthis.resetForm()\n\t\t\tthis.wizardStep = 'provider'\n\t\t\tthis.adding = true\n\t\t},\n\n\t\t/**\n\t\t * Cancel the wizard and reset.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcancelAdd() {\n\t\t\tthis.adding = false\n\t\t\tthis.resetForm()\n\t\t},\n\n\t\t/**\n\t\t * Choose a provider in step 1 and advance to the form.\n\t\t *\n\t\t * @param {string} identifier The provider identifier.\n\t\t * @return {void}\n\t\t */\n\t\tpickProvider(identifier) {\n\t\t\tthis.form.provider = identifier\n\t\t\tthis.wizardStep = 'form'\n\t\t},\n\n\t\t/**\n\t\t * Create a credential. Personal scope auto-adds the current app; org\n\t\t * scope sends the admin-chosen allowed apps and the organisation scope.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onCreate() {\n\t\t\tif (!this.canSubmit) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tconst body = {\n\t\t\t\t\tname: this.form.name.trim(),\n\t\t\t\t\tprovider: this.form.provider,\n\t\t\t\t\tsecret: this.form.secret,\n\t\t\t\t}\n\t\t\t\tif (this.scope === 'organisation') {\n\t\t\t\t\tbody.scope = 'organisation'\n\t\t\t\t\tbody.allowedApps = this.form.allowedApps.length ? this.form.allowedApps : (this.appId ? [this.appId] : [])\n\t\t\t\t} else {\n\t\t\t\t\tbody.allowedApps = this.appId ? [this.appId] : []\n\t\t\t\t}\n\t\t\t\tawait axios.post(generateUrl(CREDENTIALS_PATH), body)\n\t\t\t\tthis.cancelAdd()\n\t\t\t\tawait this.load()\n\t\t\t} catch {\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not save the credential'))\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Delete a credential (and its vault secret), then reload.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onDelete(cred) {\n\t\t\tcred.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.delete(generateUrl(`${CREDENTIALS_PATH}/${encodeURIComponent(cred.id)}`))\n\t\t\t\tawait this.load()\n\t\t\t} catch {\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not delete the credential'))\n\t\t\t\tcred.saving = false\n\t\t\t\tcred.confirmingDelete = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether the current app is allowed to use a credential.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @return {boolean} True when appId is in allowedApps.\n\t\t */\n\t\tappAllowed(cred) {\n\t\t\treturn !!this.appId && (cred.allowedApps || []).includes(this.appId)\n\t\t},\n\n\t\t/**\n\t\t * The apps other than the current one that may use a credential.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @return {Array<string>} Other app ids.\n\t\t */\n\t\totherApps(cred) {\n\t\t\treturn (cred.allowedApps || []).filter((a) => a && a !== this.appId)\n\t\t},\n\n\t\t/**\n\t\t * Personal scope: add/remove ONLY the current app to/from a credential.\n\t\t * Reverts on failure.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @param {boolean} checked The switch's new state.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync toggleThisApp(cred, checked) {\n\t\t\tif (!this.appId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst previous = cred.allowedApps.slice()\n\t\t\tconst set = new Set(previous)\n\t\t\tif (checked) {\n\t\t\t\tset.add(this.appId)\n\t\t\t} else {\n\t\t\t\tset.delete(this.appId)\n\t\t\t}\n\t\t\tconst next = Array.from(set)\n\t\t\tcred.allowedApps = next\n\t\t\tcred.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(`${CREDENTIALS_PATH}/${encodeURIComponent(cred.id)}`), { allowedApps: next })\n\t\t\t} catch {\n\t\t\t\tcred.allowedApps = previous\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not update the credential'))\n\t\t\t} finally {\n\t\t\t\tcred.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Organisation scope: persist a full allowed-apps change via PUT.\n\t\t * Reverts on failure.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @param {Array} updated The picker's new selection.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onAllowedAppsChange(cred, updated) {\n\t\t\tconst next = this.normaliseApps(updated)\n\t\t\tconst previous = cred.allowedApps.slice()\n\t\t\tcred.allowedApps = next\n\t\t\tcred.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(`${CREDENTIALS_PATH}/${encodeURIComponent(cred.id)}`), { allowedApps: next })\n\t\t\t} catch {\n\t\t\t\tcred.allowedApps = previous\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not update allowed apps'))\n\t\t\t} finally {\n\t\t\t\tcred.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable provider title — hardcoded catalogue first, then the\n\t\t * server title, then the raw identifier.\n\t\t *\n\t\t * @param {string} identifier The provider identifier.\n\t\t * @return {string} A display title.\n\t\t */\n\t\tproviderTitle(identifier) {\n\t\t\tif (PROVIDER_META[identifier] && PROVIDER_META[identifier].title) {\n\t\t\t\treturn PROVIDER_META[identifier].title\n\t\t\t}\n\t\t\tconst match = this.providers.find((p) => p.identifier === identifier)\n\t\t\treturn (match && match.title) || identifier || ''\n\t\t},\n\n\t\t/**\n\t\t * A one-letter badge for a provider tile.\n\t\t *\n\t\t * @param {string} identifier The provider identifier.\n\t\t * @return {string} An uppercase initial.\n\t\t */\n\t\tproviderInitial(identifier) {\n\t\t\tconst title = this.providerTitle(identifier)\n\t\t\treturn (title || '?').trim().charAt(0).toUpperCase() || '?'\n\t\t},\n\n\t\t/**\n\t\t * Inline style for a provider tile dot (its brand colour, or neutral).\n\t\t *\n\t\t * @param {string} identifier The provider identifier.\n\t\t * @return {object} A style binding.\n\t\t */\n\t\tdotStyle(identifier) {\n\t\t\tconst colour = (PROVIDER_META[identifier] && PROVIDER_META[identifier].colour) || 'var(--color-primary-element)'\n\t\t\treturn { backgroundColor: colour }\n\t\t},\n\n\t\t/**\n\t\t * Coerce an NcSelect multiple/taggable value into a string[] of app ids.\n\t\t *\n\t\t * @param {Array} value The picker value.\n\t\t * @return {Array<string>} App ids.\n\t\t */\n\t\tnormaliseApps(value) {\n\t\t\tif (!Array.isArray(value)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn value.map((v) => (typeof v === 'string' ? v : (v && v.id) || '')).filter((v) => v !== '')\n\t\t},\n\n\t\t/**\n\t\t * Reset the add-credential form.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetForm() {\n\t\t\tthis.form = { name: '', provider: null, secret: '', allowedApps: [] }\n\t\t},\n\n\t\t/**\n\t\t * Surface an error toast (lazy-loaded like the rest of the lib).\n\t\t *\n\t\t * @param {string} message The message to show.\n\t\t * @return {void}\n\t\t */\n\t\tshowError(message) {\n\t\t\timport('@nextcloud/dialogs').then(({ showError }) => showError(message))\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-credentials__intro {\n\tmargin-bottom: 16px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-credentials__vault-link,\n.cn-credentials__howto-link {\n\twhite-space: nowrap;\n\tcolor: var(--color-primary-element);\n}\n\n.cn-credentials__error {\n\tmargin-bottom: 12px;\n}\n\n.cn-credentials__section {\n\tmargin-bottom: 22px;\n}\n\n.cn-credentials__section-title {\n\tmargin: 8px 0 8px;\n\tfont-weight: bold;\n}\n\n.cn-credentials__muted,\n.muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-credentials__requests,\n.cn-credentials__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-credentials__request {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n}\n\n.cn-credentials__request-body {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-credentials__request-provider {\n\tfont-weight: bold;\n}\n\n.cn-credentials__request-reason {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n/* Provider tile dot: a coloured badge with the provider's initial. */\n.cn-credentials__tile-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tcolor: #fff;\n\tfont-weight: bold;\n\tfont-size: 0.95em;\n\tline-height: 1;\n}\n\n.cn-credentials__tile-dot--lg {\n\twidth: 44px;\n\theight: 44px;\n\tfont-size: 1.3em;\n}\n\n.cn-credentials__item {\n\tpadding: 12px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-credentials__item-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n}\n\n.cn-credentials__item-meta {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex-grow: 1;\n\tmin-width: 0;\n}\n\n.cn-credentials__item-name {\n\tfont-weight: bold;\n}\n\n.cn-credentials__item-provider {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-credentials__item-actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tflex-shrink: 0;\n}\n\n.cn-credentials__item-toggle {\n\tmargin: 8px 0 0 42px;\n}\n\n.cn-credentials__also {\n\tmargin: 4px 0 0;\n\tfont-size: 0.9em;\n}\n\n.cn-credentials__item-apps {\n\tmargin-top: 8px;\n\twidth: 100%;\n}\n\n.cn-credentials__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n\tgap: 10px;\n\tmargin-bottom: 12px;\n}\n\n.cn-credentials__grid-tile {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tbackground: var(--color-background-hover);\n\tborder: 2px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tcursor: pointer;\n}\n\n.cn-credentials__grid-tile:hover,\n.cn-credentials__grid-tile:focus-visible {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-background-dark);\n}\n\n.cn-credentials__grid-title {\n\tfont-weight: bold;\n\ttext-align: center;\n}\n\n.cn-credentials__wizard-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmargin-bottom: 12px;\n}\n\n.cn-credentials__wizard-title {\n\tfont-weight: bold;\n\tfont-size: 1.1em;\n}\n\n.cn-credentials__howto {\n\tmargin-bottom: 12px;\n}\n\n.cn-credentials__form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-credentials__addnote {\n\tmargin: 0;\n\tfont-size: 0.9em;\n}\n\n.cn-credentials__form-actions,\n.cn-credentials__wizard-actions {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n}\n</style>\n"],"names":["_normalizeClass","_createTextVNode","_toDisplayString","_createElementBlock","_openBlock","_renderList","_normalizeStyle","_createElementVNode","_Fragment","_createVNode","_createBlock"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnCredentials.vue3.js","sources":["../../../../src/components/CnCredentials/CnCredentials.vue?vue&type=template&id=5de04c1a&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnCredentials: the settings surface for the OpenRegister credential\n - broker. Apps occasionally need to act on your behalf against an external\n - service (GitHub, GitLab, …). Rather than hand the secret to the app, you\n - give it once to OpenRegister, which stores it in Keepiq, Nextcloud's\n - native, encrypted credential vault. Apps then make the outbound call\n - THROUGH OpenRegister and never see the secret itself. You decide which\n - apps may use each credential, so one credential can be shared across apps\n - or kept dedicated to one.\n -\n - Two scopes render from this one component:\n - • scope=\"personal\" (default), the signed-in user's own\n - credentials, shown in an app's *personal* settings. From here you may\n - only add/remove *the app you are currently in* to/from a credential,\n - or add a new credential (the current app is auto-added). You never\n - manage the full app list of another app from here.\n - • scope=\"organisation\" , organisation-wide credentials, shown in an\n - app's *admin* settings. An admin manages the full allowed-app list.\n -\n - Talks to OpenRegister's credential endpoints (metadata only, a secret is\n - write-only and is NEVER returned or displayed):\n - GET /apps/openregister/api/credentials?scope={scope}\n - GET /apps/openregister/api/credentials/providers\n - POST /apps/openregister/api/credentials { name, provider, allowedApps?, secret?, scope? }\n - PUT /apps/openregister/api/credentials/{id} { name?, allowedApps?, secret? }\n - DELETE /apps/openregister/api/credentials/{id}\n -\n - Provider presentation (title, colour, how-to text + link, secret label) is\n - a hardcoded catalogue in this file (PROVIDER_META): new providers get a\n - richer card here without a server change; unknown providers fall back to a\n - neutral tile keyed off the identifier.\n -\n - Renders inside CnAppRoot's `#user-settings` (personal) or an admin surface\n - (organisation). Fails soft: a 404 (broker not installed) degrades to an\n - empty state with a friendly note rather than crashing.\n -->\n<template>\n\t<div class=\"cn-credentials\" :class=\"`cn-credentials--${scope}`\">\n\t\t<p class=\"cn-credentials__intro\">\n\t\t\t{{ introText }}\n\t\t\t<a v-if=\"resolvedVaultUrl\"\n\t\t\t\t:href=\"resolvedVaultUrl\"\n\t\t\t\tclass=\"cn-credentials__vault-link\"\n\t\t\t\ttarget=\"_self\">\n\t\t\t\t{{ t('nextcloud-vue', 'Learn about Keepiq') }} ↗\n\t\t\t</a>\n\t\t</p>\n\n\t\t<!-- What this app uses (the app's manifest credentials[] requirements) -->\n\t\t<section v-if=\"scope === 'personal'\" class=\"cn-credentials__section cn-credentials__supports\">\n\t\t\t<h4 class=\"cn-credentials__section-title\">\n\t\t\t\t{{ t('nextcloud-vue', 'What {app} uses', { app: appDisplayName }) }}\n\t\t\t</h4>\n\t\t\t<p v-if=\"appCredentials.length === 0\" class=\"cn-credentials__muted\">\n\t\t\t\t{{ t('nextcloud-vue', 'This app does not use any external credentials.') }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-credentials__requests\">\n\t\t\t\t<li v-for=\"(req, index) in appCredentials\"\n\t\t\t\t\t:key=\"`req-${index}`\"\n\t\t\t\t\tclass=\"cn-credentials__request\">\n\t\t\t\t\t<span class=\"cn-credentials__tile-dot\" :style=\"dotStyle(req.provider)\">{{ providerInitial(req.provider) }}</span>\n\t\t\t\t\t<span class=\"cn-credentials__request-body\">\n\t\t\t\t\t\t<span class=\"cn-credentials__request-provider\">{{ providerTitle(req.provider) }}</span>\n\t\t\t\t\t\t<span v-if=\"req.reason\" class=\"cn-credentials__request-reason\">{{ req.reason }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</section>\n\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t<template v-else>\n\t\t\t<NcNoteCard v-if=\"error\"\n\t\t\t\ttype=\"warning\"\n\t\t\t\tclass=\"cn-credentials__error\">\n\t\t\t\t{{ t('nextcloud-vue', 'Credentials are provided by OpenRegister and could not be loaded here.') }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Your / the organisation's credentials -->\n\t\t\t<section class=\"cn-credentials__section\">\n\t\t\t\t<h4 class=\"cn-credentials__section-title\">\n\t\t\t\t\t{{ scope === 'organisation' ? t('nextcloud-vue', 'Organisation credentials') : t('nextcloud-vue', 'Your credentials') }}\n\t\t\t\t</h4>\n\t\t\t\t<p v-if=\"visibleCredentials.length === 0\" class=\"cn-credentials__muted\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'No credentials stored yet.') }}\n\t\t\t\t</p>\n\t\t\t\t<ul v-else class=\"cn-credentials__list\">\n\t\t\t\t\t<li v-for=\"cred in visibleCredentials\"\n\t\t\t\t\t\t:key=\"cred.id\"\n\t\t\t\t\t\tclass=\"cn-credentials__item\">\n\t\t\t\t\t\t<div class=\"cn-credentials__item-head\">\n\t\t\t\t\t\t\t<span class=\"cn-credentials__tile-dot\" :style=\"dotStyle(cred.provider)\">{{ providerInitial(cred.provider) }}</span>\n\t\t\t\t\t\t\t<div class=\"cn-credentials__item-meta\">\n\t\t\t\t\t\t\t\t<span class=\"cn-credentials__item-name\">{{ cred.name }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-credentials__item-provider\">{{ providerTitle(cred.provider) }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class=\"cn-credentials__item-actions\">\n\t\t\t\t\t\t\t\t<template v-if=\"cred.confirmingDelete\">\n\t\t\t\t\t\t\t\t\t<NcButton variant=\"error\"\n\t\t\t\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t\t\t\t@click=\"onDelete(cred)\">\n\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Confirm delete') }}\n\t\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t\t\t\t@click=\"cred.confirmingDelete = false\">\n\t\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t<NcButton v-else\n\t\t\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t\t\t@click=\"cred.confirmingDelete = true\">\n\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Delete') }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<!-- Personal: only toggle THIS app in/out of the credential. -->\n\t\t\t\t\t\t<div v-if=\"scope === 'personal'\" class=\"cn-credentials__item-toggle\">\n\t\t\t\t\t\t\t<NcCheckboxRadioSwitch :modelValue=\"appAllowed(cred)\"\n\t\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t\t\t\t@update:modelValue=\"toggleThisApp(cred, $event)\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', '{app} may use this credential', { app: appDisplayName }) }}\n\t\t\t\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t\t\t\t<p v-if=\"otherApps(cred).length\" class=\"cn-credentials__also muted\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Also allowed for: {apps}', { apps: otherApps(cred).join(', ') }) }}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<!-- Organisation: admin manages the full allowed-app list. -->\n\t\t\t\t\t\t<NcSelect v-else\n\t\t\t\t\t\t\tclass=\"cn-credentials__item-apps\"\n\t\t\t\t\t\t\t:options=\"appOptions\"\n\t\t\t\t\t\t\t:modelValue=\"cred.allowedApps\"\n\t\t\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t\t\t:taggable=\"true\"\n\t\t\t\t\t\t\tkeepOpen\n\t\t\t\t\t\t\t:disabled=\"cred.saving\"\n\t\t\t\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Allowed apps')\"\n\t\t\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'No app may use this credential yet')\"\n\t\t\t\t\t\t\t@update:modelValue=\"onAllowedAppsChange(cred, $event)\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<!-- Add credential: inline wizard: pick a provider, then fill it in. -->\n\t\t\t<section class=\"cn-credentials__section\">\n\t\t\t\t<NcButton v-if=\"!adding\"\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t@click=\"startAdd\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ t('nextcloud-vue', 'Add credential') }}\n\t\t\t\t</NcButton>\n\n\t\t\t\t<div v-else class=\"cn-credentials__wizard\">\n\t\t\t\t\t<!-- Step 1: provider icon grid -->\n\t\t\t\t\t<template v-if=\"wizardStep === 'provider'\">\n\t\t\t\t\t\t<h4 class=\"cn-credentials__section-title\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Which credential do you want to add?') }}\n\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t<div class=\"cn-credentials__grid\">\n\t\t\t\t\t\t\t<button v-for=\"p in providerGrid\"\n\t\t\t\t\t\t\t\t:key=\"p.identifier\"\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclass=\"cn-credentials__grid-tile\"\n\t\t\t\t\t\t\t\t@click=\"pickProvider(p.identifier)\">\n\t\t\t\t\t\t\t\t<span class=\"cn-credentials__tile-dot cn-credentials__tile-dot--lg\" :style=\"dotStyle(p.identifier)\">{{ providerInitial(p.identifier) }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-credentials__grid-title\">{{ p.title }}</span>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-credentials__wizard-actions\">\n\t\t\t\t\t\t\t<NcButton variant=\"tertiary\" @click=\"cancelAdd\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<!-- Step 2: provider-specific form + how-to -->\n\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t<div class=\"cn-credentials__wizard-head\">\n\t\t\t\t\t\t\t<NcButton variant=\"tertiary\" @click=\"wizardStep = 'provider'\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Back') }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t<span class=\"cn-credentials__tile-dot\" :style=\"dotStyle(form.provider)\">{{ providerInitial(form.provider) }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-credentials__wizard-title\">{{ providerTitle(form.provider) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<NcNoteCard v-if=\"activeMeta.setupHelp\" type=\"info\" class=\"cn-credentials__howto\">\n\t\t\t\t\t\t\t{{ activeMeta.setupHelp }}\n\t\t\t\t\t\t\t<a v-if=\"activeMeta.setupUrl\"\n\t\t\t\t\t\t\t\t:href=\"activeMeta.setupUrl\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t\tclass=\"cn-credentials__howto-link\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open {provider}', { provider: providerTitle(form.provider) }) }} ↗\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</NcNoteCard>\n\n\t\t\t\t\t\t<form class=\"cn-credentials__form\" @submit.prevent=\"onCreate\">\n\t\t\t\t\t\t\t<NcTextField v-model=\"form.name\"\n\t\t\t\t\t\t\t\t:label=\"t('nextcloud-vue', 'Name')\"\n\t\t\t\t\t\t\t\t:helperText=\"t('nextcloud-vue', 'A label to recognise this credential later.')\"\n\t\t\t\t\t\t\t\t:disabled=\"saving\"\n\t\t\t\t\t\t\t\trequired />\n\n\t\t\t\t\t\t\t<NcTextField v-model=\"form.secret\"\n\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t:label=\"activeMeta.secretLabel || t('nextcloud-vue', 'Secret')\"\n\t\t\t\t\t\t\t\t:helperText=\"t('nextcloud-vue', 'Sent to OpenRegister and stored in Keepiq. It is never shown again.')\"\n\t\t\t\t\t\t\t\t:disabled=\"saving\"\n\t\t\t\t\t\t\t\tautocomplete=\"new-password\"\n\t\t\t\t\t\t\t\trequired />\n\n\t\t\t\t\t\t\t<!-- Organisation add: admin also chooses the allowed apps up-front. -->\n\t\t\t\t\t\t\t<NcSelect v-if=\"scope === 'organisation'\"\n\t\t\t\t\t\t\t\t:options=\"appOptions\"\n\t\t\t\t\t\t\t\t:modelValue=\"form.allowedApps\"\n\t\t\t\t\t\t\t\t:multiple=\"true\"\n\t\t\t\t\t\t\t\t:taggable=\"true\"\n\t\t\t\t\t\t\t\tkeepOpen\n\t\t\t\t\t\t\t\t:disabled=\"saving\"\n\t\t\t\t\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Allowed apps')\"\n\t\t\t\t\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Choose which apps may use it')\"\n\t\t\t\t\t\t\t\t@update:modelValue=\"form.allowedApps = normaliseApps($event)\" />\n\t\t\t\t\t\t\t<p v-else class=\"cn-credentials__muted cn-credentials__addnote\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', '{app} will be allowed to use this credential. You can change that afterwards.', { app: appDisplayName }) }}\n\t\t\t\t\t\t\t</p>\n\n\t\t\t\t\t\t\t<div class=\"cn-credentials__form-actions\">\n\t\t\t\t\t\t\t\t<NcButton variant=\"tertiary\" :disabled=\"saving\" @click=\"cancelAdd\">\n\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\t\t\t:disabled=\"!canSubmit\">\n\t\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Add credential') }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</form>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\n\nconst CREDENTIALS_PATH = '/apps/openregister/api/credentials'\nconst PROVIDERS_PATH = '/apps/openregister/api/credentials/providers'\n\n/**\n * Hardcoded per-provider presentation. Keyed by the OpenRegister catalogue\n * identifier. `colour` drives the tile; `setupHelp`/`setupUrl` explain how to\n * obtain the secret; `secretLabel` names the field. Providers not listed here\n * still work, they render a neutral tile from their identifier.\n */\nconst PROVIDER_META = {\n\tgithub: {\n\t\ttitle: 'GitHub',\n\t\tcolour: '#1f2328',\n\t\tsetupUrl: 'https://github.com/settings/personal-access-tokens',\n\t\t// No permission level here on purpose. This text is shared by every\n\t\t// consuming app, and the level each one needs is already on screen:\n\t\t// the requests list above renders each app's own `reason`. Asserting\n\t\t// \"read-only\" contradicted Buildiq two blocks higher, which asks for\n\t\t// Administration and Contents at read AND write to publish an app, so a\n\t\t// reader who followed this line made a token that cannot publish.\n\t\tsetupHelp: 'Create a fine-grained personal access token scoped to the repositories or organisation this app should reach, granting the permissions listed above, then paste it below.',\n\t\tsecretLabel: 'Personal access token',\n\t},\n\tgitlab: {\n\t\ttitle: 'GitLab',\n\t\tcolour: '#fc6d26',\n\t\tsetupUrl: 'https://gitlab.com/-/user_settings/personal_access_tokens',\n\t\tsetupHelp: 'Create a personal access token with the read_api (and read_repository) scopes, then paste it below.',\n\t\tsecretLabel: 'Personal access token',\n\t},\n\tdoffin: {\n\t\ttitle: 'Doffin (Norway)',\n\t\tcolour: '#00509e',\n\t\tsetupUrl: 'https://dfo.no/anskaffelser/doffin',\n\t\tsetupHelp: 'Request an APIM subscription key for the Doffin public procurement API, then paste it below.',\n\t\tsecretLabel: 'Subscription key',\n\t},\n}\n\nexport default {\n\tname: 'CnCredentials',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t\tNcSelect,\n\t\tNcTextField,\n\t\tChevronLeft,\n\t\tPlus,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The consuming Nextcloud app id (e.g. \"openbuild-spectr\"). In personal\n\t\t * scope this is the only app you can toggle in/out of a credential.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * A friendly app name for copy (\"{app} may use this credential\"). Falls\n\t\t * back to the appId.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The current app's manifest `credentials[]` declarations: the\n\t\t * providers this app can reach through the broker. Shape:\n\t\t * `[{ provider, reason, scopes }]`. Rendered read-only under the intro.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\tappCredentials: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Which credential set to manage: `\"personal\"` (the signed-in user's\n\t\t * own; app-scoped toggle only) or `\"organisation\"` (org-wide; full\n\t\t * allowed-app management, admin surface).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'personal',\n\t\t\tvalidator: (v) => ['personal', 'organisation'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Optional link target explaining the Keepiq vault. Defaults to the\n\t\t * Keepiq app route; pass '' to hide the link.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tvaultUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: false,\n\t\t\tsaving: false,\n\t\t\tcredentials: [],\n\t\t\tproviders: [],\n\t\t\tadding: false,\n\t\t\twizardStep: 'provider',\n\t\t\tform: {\n\t\t\t\tname: '',\n\t\t\t\tprovider: null,\n\t\t\t\tsecret: '',\n\t\t\t\tallowedApps: [],\n\t\t\t},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Friendly current-app label.\n\t\t *\n\t\t * @return {string} App display name.\n\t\t */\n\t\tappDisplayName() {\n\t\t\treturn this.appName || this.appId || t('nextcloud-vue', 'This app')\n\t\t},\n\n\t\t/**\n\t\t * Reframed intro copy per scope. No em-dashes, sentences under 16 words.\n\t\t *\n\t\t * @spec openspec/changes/r4-object-lock-url-and-credentials-copy/specs/credentials-settings/spec.md\n\t\t * @return {string} Intro text.\n\t\t */\n\t\tintroText() {\n\t\t\tif (this.scope === 'organisation') {\n\t\t\t\treturn t('nextcloud-vue', 'Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.')\n\t\t},\n\n\t\t/**\n\t\t * Resolved Keepiq link (explicit prop, or the app route by default).\n\t\t *\n\t\t * @return {string} URL or ''.\n\t\t */\n\t\tresolvedVaultUrl() {\n\t\t\tif (this.vaultUrl === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.vaultUrl || generateUrl('/apps/doriath')\n\t\t},\n\n\t\t/**\n\t\t * The provider tiles offered in the add-wizard grid: what the server\n\t\t * actually offers when known (so we never offer a provider the broker\n\t\t * can't honour), else the full hardcoded set.\n\t\t *\n\t\t * @return {Array<{identifier: string, title: string}>} Grid tiles.\n\t\t */\n\t\tproviderGrid() {\n\t\t\tconst serverIds = this.providers.map((p) => p.identifier)\n\t\t\tlet ids = serverIds.length ? serverIds : Object.keys(PROVIDER_META)\n\t\t\t// When the app declares which providers it uses (appCredentials),\n\t\t\t// only offer those, an app should not let you add a credential for\n\t\t\t// a provider it has no code path to use.\n\t\t\tif (this.supportedProviders.size > 0) {\n\t\t\t\tids = ids.filter((id) => this.supportedProviders.has(id))\n\t\t\t}\n\t\t\treturn ids.map((id) => ({ identifier: id, title: this.providerTitle(id) }))\n\t\t},\n\n\t\t/**\n\t\t * Provider identifiers the app declares it uses, derived from\n\t\t * `appCredentials`. Empty when the app declares nothing, filtering is\n\t\t * then a no-op, so consumers that don't declare providers are unchanged.\n\t\t *\n\t\t * @return {Set<string>} Supported provider identifiers.\n\t\t */\n\t\tsupportedProviders() {\n\t\t\treturn new Set((this.appCredentials || [])\n\t\t\t\t.map((r) => r && r.provider)\n\t\t\t\t.filter(Boolean))\n\t\t},\n\n\t\t/**\n\t\t * Stored credentials to display: filtered to the app's supported\n\t\t * providers when it declares any, so you can only see and authorise\n\t\t * credentials the app can actually use; otherwise all of them.\n\t\t *\n\t\t * @return {Array<object>} Visible credentials.\n\t\t */\n\t\tvisibleCredentials() {\n\t\t\tif (this.supportedProviders.size === 0) {\n\t\t\t\treturn this.credentials\n\t\t\t}\n\t\t\treturn this.credentials.filter((c) => this.supportedProviders.has(c.provider))\n\t\t},\n\n\t\t/**\n\t\t * The union of app ids the organisation allowed-apps picker offers.\n\t\t *\n\t\t * @return {Array<string>} Distinct app ids.\n\t\t */\n\t\tappOptions() {\n\t\t\tconst ids = new Set()\n\t\t\tif (this.appId) {\n\t\t\t\tids.add(this.appId)\n\t\t\t}\n\t\t\tfor (const cred of this.credentials) {\n\t\t\t\tfor (const appId of (cred.allowedApps || [])) {\n\t\t\t\t\tif (appId) {\n\t\t\t\t\t\tids.add(appId)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Array.from(ids)\n\t\t},\n\n\t\t/**\n\t\t * Presentation metadata for the provider currently being added.\n\t\t *\n\t\t * @return {object} Provider meta (may be a neutral fallback).\n\t\t */\n\t\tactiveMeta() {\n\t\t\treturn PROVIDER_META[this.form.provider] || {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the add form can be submitted: name, provider and a secret\n\t\t * are required (the whole point is to store a secret).\n\t\t *\n\t\t * @return {boolean} True when submittable.\n\t\t */\n\t\tcanSubmit() {\n\t\t\treturn !this.saving\n\t\t\t\t&& this.form.name.trim() !== ''\n\t\t\t\t&& !!this.form.provider\n\t\t\t\t&& this.form.secret.trim() !== ''\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Load credentials (scoped) and the provider catalogue in parallel.\n\t\t * Fails soft on a rejected request.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst [credsRes, provRes] = await Promise.all([\n\t\t\t\t\taxios.get(generateUrl(CREDENTIALS_PATH), { params: { scope: this.scope } }),\n\t\t\t\t\taxios.get(generateUrl(PROVIDERS_PATH)),\n\t\t\t\t])\n\t\t\t\tthis.credentials = this.mapCredentials(credsRes?.data?.results)\n\t\t\t\tthis.providers = Array.isArray(provRes?.data?.results) ? provRes.data.results : []\n\t\t\t} catch {\n\t\t\t\tthis.error = true\n\t\t\t\tthis.credentials = []\n\t\t\t\tthis.providers = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Normalise API credential objects into display rows with local state.\n\t\t *\n\t\t * @param {Array<object>} results Raw credential objects.\n\t\t * @return {Array<object>} Display rows.\n\t\t */\n\t\tmapCredentials(results) {\n\t\t\tif (!Array.isArray(results)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.map((c) => ({\n\t\t\t\tid: c.id,\n\t\t\t\tname: c.name || '',\n\t\t\t\tprovider: c.provider || '',\n\t\t\t\tallowedApps: Array.isArray(c.allowedApps) ? c.allowedApps.slice() : [],\n\t\t\t\tsaving: false,\n\t\t\t\tconfirmingDelete: false,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Open the add-credential wizard at the provider grid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartAdd() {\n\t\t\tthis.resetForm()\n\t\t\tthis.wizardStep = 'provider'\n\t\t\tthis.adding = true\n\t\t},\n\n\t\t/**\n\t\t * Cancel the wizard and reset.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcancelAdd() {\n\t\t\tthis.adding = false\n\t\t\tthis.resetForm()\n\t\t},\n\n\t\t/**\n\t\t * Choose a provider in step 1 and advance to the form.\n\t\t *\n\t\t * @param {string} identifier The provider identifier.\n\t\t * @return {void}\n\t\t */\n\t\tpickProvider(identifier) {\n\t\t\tthis.form.provider = identifier\n\t\t\tthis.wizardStep = 'form'\n\t\t},\n\n\t\t/**\n\t\t * Create a credential. Personal scope auto-adds the current app; org\n\t\t * scope sends the admin-chosen allowed apps and the organisation scope.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onCreate() {\n\t\t\tif (!this.canSubmit) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tconst body = {\n\t\t\t\t\tname: this.form.name.trim(),\n\t\t\t\t\tprovider: this.form.provider,\n\t\t\t\t\tsecret: this.form.secret,\n\t\t\t\t}\n\t\t\t\tif (this.scope === 'organisation') {\n\t\t\t\t\tbody.scope = 'organisation'\n\t\t\t\t\tbody.allowedApps = this.form.allowedApps.length ? this.form.allowedApps : (this.appId ? [this.appId] : [])\n\t\t\t\t} else {\n\t\t\t\t\tbody.allowedApps = this.appId ? [this.appId] : []\n\t\t\t\t}\n\t\t\t\tawait axios.post(generateUrl(CREDENTIALS_PATH), body)\n\t\t\t\tthis.cancelAdd()\n\t\t\t\tawait this.load()\n\t\t\t} catch {\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not save the credential'))\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Delete a credential (and its vault secret), then reload.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onDelete(cred) {\n\t\t\tcred.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.delete(generateUrl(`${CREDENTIALS_PATH}/${encodeURIComponent(cred.id)}`))\n\t\t\t\tawait this.load()\n\t\t\t} catch {\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not delete the credential'))\n\t\t\t\tcred.saving = false\n\t\t\t\tcred.confirmingDelete = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Whether the current app is allowed to use a credential.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @return {boolean} True when appId is in allowedApps.\n\t\t */\n\t\tappAllowed(cred) {\n\t\t\treturn !!this.appId && (cred.allowedApps || []).includes(this.appId)\n\t\t},\n\n\t\t/**\n\t\t * The apps other than the current one that may use a credential.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @return {Array<string>} Other app ids.\n\t\t */\n\t\totherApps(cred) {\n\t\t\treturn (cred.allowedApps || []).filter((a) => a && a !== this.appId)\n\t\t},\n\n\t\t/**\n\t\t * Personal scope: add/remove ONLY the current app to/from a credential.\n\t\t * Reverts on failure.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @param {boolean} checked The switch's new state.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync toggleThisApp(cred, checked) {\n\t\t\tif (!this.appId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst previous = cred.allowedApps.slice()\n\t\t\tconst set = new Set(previous)\n\t\t\tif (checked) {\n\t\t\t\tset.add(this.appId)\n\t\t\t} else {\n\t\t\t\tset.delete(this.appId)\n\t\t\t}\n\t\t\tconst next = Array.from(set)\n\t\t\tcred.allowedApps = next\n\t\t\tcred.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(`${CREDENTIALS_PATH}/${encodeURIComponent(cred.id)}`), { allowedApps: next })\n\t\t\t} catch {\n\t\t\t\tcred.allowedApps = previous\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not update the credential'))\n\t\t\t} finally {\n\t\t\t\tcred.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Organisation scope: persist a full allowed-apps change via PUT.\n\t\t * Reverts on failure.\n\t\t *\n\t\t * @param {object} cred The credential row.\n\t\t * @param {Array} updated The picker's new selection.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onAllowedAppsChange(cred, updated) {\n\t\t\tconst next = this.normaliseApps(updated)\n\t\t\tconst previous = cred.allowedApps.slice()\n\t\t\tcred.allowedApps = next\n\t\t\tcred.saving = true\n\t\t\ttry {\n\t\t\t\tawait axios.put(generateUrl(`${CREDENTIALS_PATH}/${encodeURIComponent(cred.id)}`), { allowedApps: next })\n\t\t\t} catch {\n\t\t\t\tcred.allowedApps = previous\n\t\t\t\tthis.showError(t('nextcloud-vue', 'Could not update allowed apps'))\n\t\t\t} finally {\n\t\t\t\tcred.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Human-readable provider title: hardcoded catalogue first, then the\n\t\t * server title, then the raw identifier.\n\t\t *\n\t\t * @param {string} identifier The provider identifier.\n\t\t * @return {string} A display title.\n\t\t */\n\t\tproviderTitle(identifier) {\n\t\t\tif (PROVIDER_META[identifier] && PROVIDER_META[identifier].title) {\n\t\t\t\treturn PROVIDER_META[identifier].title\n\t\t\t}\n\t\t\tconst match = this.providers.find((p) => p.identifier === identifier)\n\t\t\treturn (match && match.title) || identifier || ''\n\t\t},\n\n\t\t/**\n\t\t * A one-letter badge for a provider tile.\n\t\t *\n\t\t * @param {string} identifier The provider identifier.\n\t\t * @return {string} An uppercase initial.\n\t\t */\n\t\tproviderInitial(identifier) {\n\t\t\tconst title = this.providerTitle(identifier)\n\t\t\treturn (title || '?').trim().charAt(0).toUpperCase() || '?'\n\t\t},\n\n\t\t/**\n\t\t * Inline style for a provider tile dot (its brand colour, or neutral).\n\t\t *\n\t\t * @param {string} identifier The provider identifier.\n\t\t * @return {object} A style binding.\n\t\t */\n\t\tdotStyle(identifier) {\n\t\t\tconst colour = (PROVIDER_META[identifier] && PROVIDER_META[identifier].colour) || 'var(--color-primary-element)'\n\t\t\treturn { backgroundColor: colour }\n\t\t},\n\n\t\t/**\n\t\t * Coerce an NcSelect multiple/taggable value into a string[] of app ids.\n\t\t *\n\t\t * @param {Array} value The picker value.\n\t\t * @return {Array<string>} App ids.\n\t\t */\n\t\tnormaliseApps(value) {\n\t\t\tif (!Array.isArray(value)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn value.map((v) => (typeof v === 'string' ? v : (v && v.id) || '')).filter((v) => v !== '')\n\t\t},\n\n\t\t/**\n\t\t * Reset the add-credential form.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetForm() {\n\t\t\tthis.form = { name: '', provider: null, secret: '', allowedApps: [] }\n\t\t},\n\n\t\t/**\n\t\t * Surface an error toast (lazy-loaded like the rest of the lib).\n\t\t *\n\t\t * @param {string} message The message to show.\n\t\t * @return {void}\n\t\t */\n\t\tshowError(message) {\n\t\t\timport('@nextcloud/dialogs').then(({ showError }) => showError(message))\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-credentials__intro {\n\tmargin-bottom: 16px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-credentials__vault-link,\n.cn-credentials__howto-link {\n\twhite-space: nowrap;\n\tcolor: var(--color-primary-element);\n}\n\n.cn-credentials__error {\n\tmargin-bottom: 12px;\n}\n\n.cn-credentials__section {\n\tmargin-bottom: 22px;\n}\n\n.cn-credentials__section-title {\n\tmargin: 8px 0 8px;\n\tfont-weight: bold;\n}\n\n.cn-credentials__muted,\n.muted {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-credentials__requests,\n.cn-credentials__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-credentials__request {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n}\n\n.cn-credentials__request-body {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-credentials__request-provider {\n\tfont-weight: bold;\n}\n\n.cn-credentials__request-reason {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n/* Provider tile dot: a coloured badge with the provider's initial. */\n.cn-credentials__tile-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tcolor: #fff;\n\tfont-weight: bold;\n\tfont-size: 0.95em;\n\tline-height: 1;\n}\n\n.cn-credentials__tile-dot--lg {\n\twidth: 44px;\n\theight: 44px;\n\tfont-size: 1.3em;\n}\n\n.cn-credentials__item {\n\tpadding: 12px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-credentials__item-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n}\n\n.cn-credentials__item-meta {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex-grow: 1;\n\tmin-width: 0;\n}\n\n.cn-credentials__item-name {\n\tfont-weight: bold;\n}\n\n.cn-credentials__item-provider {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-credentials__item-actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tflex-shrink: 0;\n}\n\n.cn-credentials__item-toggle {\n\tmargin: 8px 0 0 42px;\n}\n\n.cn-credentials__also {\n\tmargin: 4px 0 0;\n\tfont-size: 0.9em;\n}\n\n.cn-credentials__item-apps {\n\tmargin-top: 8px;\n\twidth: 100%;\n}\n\n.cn-credentials__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n\tgap: 10px;\n\tmargin-bottom: 12px;\n}\n\n.cn-credentials__grid-tile {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tbackground: var(--color-background-hover);\n\tborder: 2px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tcursor: pointer;\n}\n\n.cn-credentials__grid-tile:hover,\n.cn-credentials__grid-tile:focus-visible {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-background-dark);\n}\n\n.cn-credentials__grid-title {\n\tfont-weight: bold;\n\ttext-align: center;\n}\n\n.cn-credentials__wizard-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmargin-bottom: 12px;\n}\n\n.cn-credentials__wizard-title {\n\tfont-weight: bold;\n\tfont-size: 1.1em;\n}\n\n.cn-credentials__howto {\n\tmargin-bottom: 12px;\n}\n\n.cn-credentials__form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-credentials__addnote {\n\tmargin: 0;\n\tfont-size: 0.9em;\n}\n\n.cn-credentials__form-actions,\n.cn-credentials__wizard-actions {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n}\n</style>\n"],"names":["_normalizeClass","_createTextVNode","_toDisplayString","_createElementBlock","_openBlock","_renderList","_normalizeStyle","_createElementVNode","_Fragment","_createVNode","_createBlock"],"mappings":"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"}
|
|
@@ -1522,18 +1522,21 @@ var script = {
|
|
|
1522
1522
|
() => props.objectId,
|
|
1523
1523
|
{ enabled: () => Boolean(resolveType() && props.objectId) },
|
|
1524
1524
|
);
|
|
1525
|
-
|
|
1526
|
-
|
|
1525
|
+
// Getters, read at call time: a register or schema that arrives after
|
|
1526
|
+
// mount (route params, a resolved sidebar) must reach the lock URL. They
|
|
1527
|
+
// used to be consts captured once at setup.
|
|
1528
|
+
const sidebarReg = () => props.sidebarProps?.register || props.resolvedSidebar?.register || props.register || '';
|
|
1529
|
+
const sidebarSchema = () => props.sidebarProps?.schema || props.resolvedSidebar?.schema || props.schema || '';
|
|
1527
1530
|
const lock = useObjectLock(
|
|
1528
1531
|
subscriptionStore,
|
|
1529
|
-
|
|
1530
|
-
() => resolveType() || sidebarSchema,
|
|
1532
|
+
sidebarReg,
|
|
1533
|
+
() => resolveType() || sidebarSchema(),
|
|
1531
1534
|
() => props.objectId,
|
|
1532
1535
|
// The third argument is the OBJECT CACHE KEY (`<register>-<schema>`)
|
|
1533
1536
|
// and the lock URL needs the schema SLUG. They were one string, and
|
|
1534
1537
|
// the acquire went to `/api/objects/<register>/<register>-<schema>/`,
|
|
1535
1538
|
// which no register answers.
|
|
1536
|
-
{ schemaSlug: () => props.schema || sidebarSchema },
|
|
1539
|
+
{ schemaSlug: () => props.schema || sidebarSchema() },
|
|
1537
1540
|
);
|
|
1538
1541
|
return {
|
|
1539
1542
|
...registryExposed,
|