diff --git a/apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue b/apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue
index 3d3ccc29aac77..19a1b7ed8cd0d 100644
--- a/apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue
+++ b/apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue
@@ -18,6 +18,7 @@ import BadgeAppLevel from '../BadgeAppLevel.vue'
import BadgeAppScore from '../BadgeAppScore.vue'
import { useLimitedGroups } from '../../composables/useLimitedGroups.ts'
import { useAppsStore } from '../../store/apps.ts'
+import { canLimitToGroups } from '../../utils/appStatus.ts'
const { app } = defineProps<{ app: IAppstoreApp | IAppstoreExApp }>()
@@ -98,6 +99,10 @@ const appCategories = computed(() => {
.join(', ')
})
+const cannotLimitToGroups = computed(() => {
+ return app.active && !canLimitToGroups(app)
+})
+
/**
* Get the author name from the XML node
*
@@ -138,6 +143,10 @@ function authorName(xmlNode): string {
+
'+(n?a:V(a,!0))+`
`:""+(n?a:V(a,!0))+`
`}blockquote({tokens:e}){return`@@ -56,6 +56,6 @@ ${this.parser.parse(e)}`}tablerow({text:e}){return`
${V(e,!0)}`}br(e){return"An error occurred:
"+V(n.message+"",!0)+"";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}},ae=new Ls;function C(e,t){return ae.parse(e,t)}C.options=C.setOptions=function(e){return ae.setOptions(e),C.defaults=ae.defaults,nt(C.defaults),C},C.getDefaults=Ue,C.defaults=le,C.use=function(...e){return ae.use(...e),C.defaults=ae.defaults,nt(C.defaults),C},C.walkTokens=function(e,t){return ae.walkTokens(e,t)},C.parseInline=ae.parseInline,C.Parser=F,C.parser=F.parse,C.Renderer=ze,C.TextRenderer=et,C.Lexer=U,C.lexer=U.lex,C.Tokenizer=De,C.Hooks=me,C.parse=C,C.options,C.setOptions,C.use,C.walkTokens,C.parseInline,F.parse,U.lex;function Es(e,t){const n=new C.Renderer;return n.blockquote=qs,n.link=Bs,n.image=Ps,$(()=>{const s=t?.minHeadingLevel??1;n.heading=Ns(s);const a=fe(e).trim();return aa.sanitize(C(a,{async:!1,renderer:n,gfm:!1,breaks:!1,pedantic:!1}),{ALLOWED_TAGS:["h1","h2","h3","h4","h5","h6","strong","p","a","ul","ol","li","em","del","blockquote"]})})}function Bs({href:e,title:t,text:n}){let s;try{s=new URL(e)}catch{return""}if(s.protocol!=="http:"&&s.protocol!=="https:")return"";let a='"+n.replaceAll(/(?",a}function Ps({title:e,text:t}){return t||(e??"")}function qs({text:e}){return`
${e}`}function Ns(e){return({text:t,depth:n})=>(n=Math.min(6,n+(e-1)),`
${V(e,!0)}`}br(e){return"An error occurred:
"+V(n.message+"",!0)+"";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}},ae=new Ls;function C(e,t){return ae.parse(e,t)}C.options=C.setOptions=function(e){return ae.setOptions(e),C.defaults=ae.defaults,lt(C.defaults),C},C.getDefaults=Fe,C.defaults=le,C.use=function(...e){return ae.use(...e),C.defaults=ae.defaults,lt(C.defaults),C},C.walkTokens=function(e,t){return ae.walkTokens(e,t)},C.parseInline=ae.parseInline,C.Parser=F,C.parser=F.parse,C.Renderer=ze,C.TextRenderer=tt,C.Lexer=U,C.lexer=U.lex,C.Tokenizer=De,C.Hooks=me,C.parse=C,C.options,C.setOptions,C.use,C.walkTokens,C.parseInline,F.parse,U.lex;function Es(e,t){const n=new C.Renderer;return n.blockquote=qs,n.link=Bs,n.image=Ps,$(()=>{const s=t?.minHeadingLevel??1;n.heading=Ns(s);const a=fe(e).trim();return na.sanitize(C(a,{async:!1,renderer:n,gfm:!1,breaks:!1,pedantic:!1}),{ALLOWED_TAGS:["h1","h2","h3","h4","h5","h6","strong","p","a","ul","ol","li","em","del","blockquote"]})})}function Bs({href:e,title:t,text:n}){let s;try{s=new URL(e)}catch{return""}if(s.protocol!=="http:"&&s.protocol!=="https:")return"";let a='"+n.replaceAll(/(?",a}function Ps({title:e,text:t}){return t||(e??"")}function qs({text:e}){return`
${e}`}function Ns(e){return({text:t,depth:n})=>(n=Math.min(6,n+(e-1)),`
{{ t('appstore', 'Type') }}: {{ app?.daemon.accepts_deploy_id }}
\n\t\t\t{{ t('appstore', 'Name') }}: {{ app?.daemon.name }}
\n\t\t\t{{ t('appstore', 'Display Name') }}: {{ app?.daemon.display_name }}
\n\t\t\t{{ t('appstore', 'GPUs support') }}: {{ gpuSupport }}
\n\t\t\t{{ t('appstore', 'Compute device') }}: {{ app?.daemon?.deploy_config?.computeDevice?.label }}
\n\t\t'+(n?i:T(i,!0))+`\n`:\"\"+(n?i:T(i,!0))+`\n`}blockquote({tokens:e}){return`\n${this.parser.parse(e)}\n`}html({text:e}){return e}def(e){return\"\"}heading({tokens:e,depth:t}){return`
${this.parser.parseInline(e)}
\n`}table(e){let t=\"\",n=\"\";for(let i=0;i${T(e,!0)}`}br(e){return\"An error occurred:
\"+T(n.message+\"\",!0)+\"\";return t?Promise.resolve(r):r}if(t)return Promise.reject(n);throw n}}};var L=new D;function g(u,e){return L.parse(u,e)}g.options=g.setOptions=function(u){return L.setOptions(u),g.defaults=L.defaults,G(g.defaults),g};g.getDefaults=M;g.defaults=O;g.use=function(...u){return L.use(...u),g.defaults=L.defaults,G(g.defaults),g};g.walkTokens=function(u,e){return L.walkTokens(u,e)};g.parseInline=L.parseInline;g.Parser=b;g.parser=b.parse;g.Renderer=y;g.TextRenderer=$;g.Lexer=x;g.lexer=x.lex;g.Tokenizer=w;g.Hooks=P;g.parse=g;var Qt=g.options,jt=g.setOptions,Ft=g.use,Ut=g.walkTokens,Kt=g.parseInline,Wt=g,Xt=b.parse,Jt=x.lex;export{P as Hooks,x as Lexer,D as Marked,b as Parser,y as Renderer,$ as TextRenderer,w as Tokenizer,O as defaults,M as getDefaults,Jt as lexer,g as marked,Qt as options,Wt as parse,Kt as parseInline,Xt as parser,jt as setOptions,Ft as use,Ut as walkTokens};\n//# sourceMappingURL=marked.esm.js.map\n","/*!\n * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { Tokens } from 'marked'\nimport type { MaybeRefOrGetter } from 'vue'\n\nimport dompurify from 'dompurify'\nimport { marked } from 'marked'\nimport { computed, toValue } from 'vue'\n\nexport interface MarkdownOptions {\n\tminHeadingLevel?: number\n}\n\n/**\n * Render Markdown to HTML\n *\n * @param text - The Markdown source\n * @param options - Markdown options\n */\nexport function useMarkdown(text: MaybeRefOrGetter
${text}`\n}\n\n/**\n * Get a custom heading renderer that clamps heading levels\n *\n * @param minHeading - The heading to clamp to\n */\nfunction getMarkedHeading(minHeading: number) {\n\t/**\n\t * Custom heading renderer that adjusts heading levels\n\t *\n\t * @param ctx - The render context\n\t * @param ctx.text - The heading text\n\t * @param ctx.depth - The heading depth\n\t */\n\treturn ({ text, depth }: Tokens.Heading): string => {\n\t\tdepth = Math.min(6, depth + (minHeading - 1))\n\t\treturn `
\n\t\t\t\t\t{{ appAuthors }}\n\t\t\t\t
\n\t\t\t\n\t\t\t\t\t{{ appCategories }}\n\t\t\t\t
\n\t\t\t{{ t('appstore', 'Restrict the usage of {app} to members of the following groups.', { app: app.name }) }}
\n\t\t| {{ t('appstore', 'App name') }} | \n\t\t\t\t{{ t('appstore', 'Version') }} | \n\t\t\t\t\n\t\t\t\t\t{{ t('appstore', 'Support level') }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ t('appstore', 'Groups') }}\n\t\t\t\t | \n\t\t\t\t{{ t('appstore', 'Actions') }} | \n\t\t\t
|---|
{{ t('appstore', 'Type') }}: {{ app?.daemon.accepts_deploy_id }}
\n\t\t\t{{ t('appstore', 'Name') }}: {{ app?.daemon.name }}
\n\t\t\t{{ t('appstore', 'Display Name') }}: {{ app?.daemon.display_name }}
\n\t\t\t{{ t('appstore', 'GPUs support') }}: {{ gpuSupport }}
\n\t\t\t{{ t('appstore', 'Compute device') }}: {{ app?.daemon?.deploy_config?.computeDevice?.label }}
\n\t\t'+(n?i:T(i,!0))+`\n`:\"\"+(n?i:T(i,!0))+`\n`}blockquote({tokens:e}){return`\n${this.parser.parse(e)}\n`}html({text:e}){return e}def(e){return\"\"}heading({tokens:e,depth:t}){return`
${this.parser.parseInline(e)}
\n`}table(e){let t=\"\",n=\"\";for(let i=0;i${T(e,!0)}`}br(e){return\"An error occurred:
\"+T(n.message+\"\",!0)+\"\";return t?Promise.resolve(r):r}if(t)return Promise.reject(n);throw n}}};var L=new D;function g(u,e){return L.parse(u,e)}g.options=g.setOptions=function(u){return L.setOptions(u),g.defaults=L.defaults,G(g.defaults),g};g.getDefaults=M;g.defaults=O;g.use=function(...u){return L.use(...u),g.defaults=L.defaults,G(g.defaults),g};g.walkTokens=function(u,e){return L.walkTokens(u,e)};g.parseInline=L.parseInline;g.Parser=b;g.parser=b.parse;g.Renderer=y;g.TextRenderer=$;g.Lexer=x;g.lexer=x.lex;g.Tokenizer=w;g.Hooks=P;g.parse=g;var Qt=g.options,jt=g.setOptions,Ft=g.use,Ut=g.walkTokens,Kt=g.parseInline,Wt=g,Xt=b.parse,Jt=x.lex;export{P as Hooks,x as Lexer,D as Marked,b as Parser,y as Renderer,$ as TextRenderer,w as Tokenizer,O as defaults,M as getDefaults,Jt as lexer,g as marked,Qt as options,Wt as parse,Kt as parseInline,Xt as parser,jt as setOptions,Ft as use,Ut as walkTokens};\n//# sourceMappingURL=marked.esm.js.map\n","/*!\n * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport type { Tokens } from 'marked'\nimport type { MaybeRefOrGetter } from 'vue'\n\nimport dompurify from 'dompurify'\nimport { marked } from 'marked'\nimport { computed, toValue } from 'vue'\n\nexport interface MarkdownOptions {\n\tminHeadingLevel?: number\n}\n\n/**\n * Render Markdown to HTML\n *\n * @param text - The Markdown source\n * @param options - Markdown options\n */\nexport function useMarkdown(text: MaybeRefOrGetter
${text}`\n}\n\n/**\n * Get a custom heading renderer that clamps heading levels\n *\n * @param minHeading - The heading to clamp to\n */\nfunction getMarkedHeading(minHeading: number) {\n\t/**\n\t * Custom heading renderer that adjusts heading levels\n\t *\n\t * @param ctx - The render context\n\t * @param ctx.text - The heading text\n\t * @param ctx.depth - The heading depth\n\t */\n\treturn ({ text, depth }: Tokens.Heading): string => {\n\t\tdepth = Math.min(6, depth + (minHeading - 1))\n\t\treturn `
\n\t\t\t\t\t{{ appAuthors }}\n\t\t\t\t
\n\t\t\t\n\t\t\t\t\t{{ appCategories }}\n\t\t\t\t
\n\t\t\t{{ t('appstore', 'Restrict the usage of {app} to members of the following groups.', { app: app.name }) }}
\n\t\t| {{ t('appstore', 'App name') }} | \n\t\t\t\t{{ t('appstore', 'Version') }} | \n\t\t\t\t\n\t\t\t\t\t{{ t('appstore', 'Support level') }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ t('appstore', 'Groups') }}\n\t\t\t\t | \n\t\t\t\t{{ t('appstore', 'Actions') }} | \n\t\t\t
|---|