Skip to content

chore(web-ui): rebuild admin SPA against @phlix/ui v0.79.0 (v1.2.2)#524

Merged
detain merged 1 commit into
masterfrom
chore/spa-bundle-0.79.0
Jul 11, 2026
Merged

chore(web-ui): rebuild admin SPA against @phlix/ui v0.79.0 (v1.2.2)#524
detain merged 1 commit into
masterfrom
chore/spa-bundle-0.79.0

Conversation

@detain

@detain detain commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Wires the phlix-ui v0.79.0 plugin-configure UX into the server's served SPA: bumps web-ui/'s @phlix/ui pin v0.78.0v0.79.0 and regenerates the committed public/assets/app/ bundle (visible enable-failure banner, secret set/length status, field-help links/defaults). Version 1.2.11.2.2.

🤖 Generated with Claude Code

Bumps web-ui's @phlix/ui pin v0.78.0 -> v0.79.0 and regenerates the committed
public/assets/app/ bundle, so the deployed server actually serves the plugin
configure/lifecycle UX (visible enable errors, secret set/length status,
field-help links). Version 1.2.1 -> 1.2.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 5 medium · 1 minor

Alerts:
⚠ 6 issues (≤ 0 issues of at least minor severity)

Results:
6 new issues

Category Results
CodeStyle 1 minor
Complexity 5 medium

View in Codacy

🟢 Metrics 1669 complexity · 0 duplication

Metric Results
Complexity 1669
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a99a4aa30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,2 @@
import{A as e,B as t,D as n,J as r,M as i,_ as ee,c as a,h as o,l as s,m as c,o as l,r as u,s as d,u as f,x as te,xt as p,yt as ne,z as m}from"./runtime-core.esm-bundler-Del-PMg5.js";import{i as re,r as ie,s as ae}from"./runtime-dom.esm-bundler-Bwq0qijf.js";import{n as oe,t as se}from"./Icon-Bd1lZf6E-Bv7comGB.js";import{t as h}from"./Modal-DnGvbsI9-C5Ii3rq7.js";import{n as ce,o as g,s as le}from"./client-DH50wjeq-Bx47DlfG.js";import{t as ue}from"./useToastStore-BDoKlU6N-LhUU0Qm7.js";import{t as _}from"./Button-CnyfCnhY-OTruTKEt.js";import{t as v}from"./Badge-Dq-pYhrz-CAIMOWAu.js";import{t as y}from"./Switch-B9lejr6_-x5ApHDOZ.js";import{t as de}from"./Skeleton-CzU_l53W-DZIXnGks.js";import{t as fe}from"./EmptyState-588Z_81C-BpSzenOt.js";import{t as pe}from"./PageHint-7Giwob0l-C0xibLs-.js";import{g as me,h as he,m as ge}from"./index-BuhIOrml.js";var _e={class:`admin-plugins`,"aria-labelledby":`plugins-heading`},ve={class:`admin-plugins__head`},ye={class:`admin-plugins__head-actions`},be={class:`admin-plugins__sources`,"aria-label":`Catalog sources`},xe=[`href`],Se=[`disabled`,`aria-label`,`onClick`],Ce={key:0,class:`admin-plugins__install-error`,role:`alert`},we={class:`admin-plugins__install-error-body`},Te={key:1,class:`admin-plugins__install-error`,role:`alert`},Ee={class:`admin-plugins__install-error-body`},De={key:2,class:`admin-plugins__skel`},Oe={key:1,class:`admin-plugins__grid`,"aria-label":`Catalog plugins`},ke={class:`admin-plugins__card-head`},Ae={class:`admin-plugins__card-title`},je={class:`admin-plugins__card-badges`},Me={key:0,class:`admin-plugins__card-summary`},Ne={key:1,class:`admin-plugins__card-tags`},Pe={class:`admin-plugins__card-actions`},Fe=[`href`],Ie={key:4,class:`admin-plugins__orphans`,"aria-labelledby":`orphans-heading`},Le={class:`admin-plugins__table`,"aria-label":`Other installed plugins`},Re={class:`admin-plugins__mono`},ze={class:`admin-plugins__actions`},Be={class:`admin-plugins__field`},Ve={class:`admin-plugins__field`},He={key:0,class:`admin-plugins__skel`},Ue={class:`admin-plugins__label`},We={key:0,class:`admin-plugins__req`,"aria-hidden":`true`,title:`Required`},Ge={key:1,class:`admin-plugins__optional`},Ke=[`onUpdate:modelValue`,`type`,`autocomplete`,`placeholder`,`aria-label`,`aria-invalid`],qe={class:`admin-plugins__secret-dots`,"aria-hidden":`true`},Je={key:2,class:`admin-plugins__hint`},Ye={key:3,class:`admin-plugins__hint admin-plugins__default-hint`},Xe=[`href`],Ze={key:5,class:`admin-plugins__error`,role:`alert`},b=oe(ee({__name:`PluginsPage`,props:{client:{}},setup(ee){let oe=ee,b=te(`apiBase`,``),Qe=l(()=>typeof b==`string`?b:b?.value??``),x=new ge(oe.client??new le({baseUrl:Qe.value,tokenStore:new ce})),S=ue(),$e=r([]),C=r(!0),w=r(null);async function et(){C.value=!0,w.value=null;try{$e.value=await x.list()}catch(e){w.value=g(e,`Failed to load plugins.`),S.error(w.value)}finally{C.value=!1}}let T=r({default_source:``,sources:[],catalogs:[],errors:[]}),tt=r(!0);async function E(){tt.value=!0;try{T.value=await x.catalog()}catch(e){S.error(g(e,`Failed to load the plugin catalog.`))}finally{tt.value=!1}}async function D(){await Promise.all([et(),E()])}let O=l(()=>{let e=new Set,t=[];for(let n of T.value.catalogs)for(let r of n.plugins)e.has(r.name)||(e.add(r.name),t.push(r));return t}),nt=l(()=>new Set(O.value.map(e=>e.name))),rt=l(()=>$e.value.filter(e=>!nt.value.has(e.name)));function it(e){let t=e.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?\/?$/i);if(t)return t[1];try{return new URL(e).host}catch{return e}}function k(e){return{name:e.name,version:``,type:e.type,enabled:e.enabled}}let A=r(!1),j=r(``),at=r(!1);function ot(e){switch(he(e)){case`plugin.url.required`:return`A plugin URL is required.`;case`plugin.url.invalid_scheme`:return`That does not look like a valid plugin URL (use https://…).`;case`plugin.install.failed`:return g(e,`Install failed — the plugin could not be downloaded or read.`);default:return g(e,`Failed to install plugin.`)}}function st(){j.value=``,A.value=!0}function ct(){A.value=!1,j.value=``}let M=r(null);function lt(e){let t=ot(e);M.value=t,S.error(t)}async function ut(){let e=j.value.trim();if(!e){S.error(`A plugin URL is required.`);return}at.value=!0,M.value=null;try{await x.install(e),S.success(`Plugin installed.`),ct(),await D()}catch(e){lt(e)}finally{at.value=!1}}let N=r(null);async function dt(e){if(N.value===null){N.value=e.repo,M.value=null;try{await x.install(e.repo),S.success(`${e.title} installed.`),await D()}catch(e){lt(e)}finally{N.value=null}}}let P=r(!1),F=r(``),ft=r(!1),I=r(null);function pt(){F.value=``,P.value=!0}function mt(){P.value=!1,F.value=``}async function ht(){let e=F.value.trim();if(!e){S.error(`A catalog URL is required.`);return}ft.value=!0;try{await x.addCatalogSource(e),S.success(`Catalog added.`),mt(),await E()}catch(e){let t=he(e);S.error(t===`plugin.catalog.url.invalid`?`That catalog URL is not valid (use an http(s):// URL).`:g(e,`Failed to add catalog.`))}finally{ft.value=!1}}async function gt(e){if(I.value===null){I.value=e;try{await x.removeCatalogSource(e),S.success(`Catalog removed.`),await E()}catch(e){S.error(g(e,`Failed to remove catalog.`))}finally{I.value=null}}}let L=r({}),R=r(!1),z=r(!1),B=r(null),V=r(!1),_t=l(()=>Object.values(L.value).filter(e=>e.update_available).length);function H(e){let t=L.value[e];return t&&t.update_available?t:null}function vt(e){let t={};for(let n of e)n&&typeof n.name==`string`&&n.update_available&&(t[n.name]=n);L.value=t}function yt(e){if(e in L.value){let t={...L.value};delete t[e],L.value=t}}async function bt(){if(!R.value){R.value=!0;try{let e=await x.checkUpdates();vt(e.updates),V.value=e.auto_update;let t=_t.value;S.success(t>0?`${t} update${t===1?``:`s`} available.`:`All plugins are up to date.`)}catch(e){S.error(g(e,`Failed to check for updates.`))}finally{R.value=!1}}}function xt(e){switch(he(e)){case`plugin.update.no_source`:return`This plugin has no update source — reinstall it from a URL to update.`;case`plugin.update.failed`:return g(e,`Update failed — the new version could not be downloaded or read.`);default:return g(e,`Failed to update plugin.`)}}async function St(e){if(B.value===null){B.value=e.name,U.value=null;try{await x.updatePlugin(e.name),S.success(`${e.name} updated.`),yt(e.name),await D()}catch(t){U.value={title:`Couldn't update ${e.name}`,message:xt(t)},S.error(xt(t))}finally{B.value=null}}}async function Ct(){if(!z.value){z.value=!0;try{let e=await x.updateAll();e.failed.length>0?S.error(`${e.updated.length} updated, ${e.failed.length} failed.`):S.success(`${e.updated.length} plugin${e.updated.length===1?``:`s`} updated.`),await D();let t=await x.checkUpdates();vt(t.updates),V.value=t.auto_update}catch(e){S.error(g(e,`Failed to apply updates.`))}finally{z.value=!1}}}async function wt(e){let t=V.value;V.value=e;try{V.value=await x.setAutoUpdate(e),S.success(e?`Auto-update enabled.`:`Auto-update disabled.`)}catch(e){V.value=t,S.error(g(e,`Failed to change auto-update.`))}}let U=r(null);function Tt(e,t,n){let r=g(t,n);U.value={title:e,message:r},S.error(r)}let W=r(null);async function Et(e,t){if(W.value===null){W.value=e.name,U.value=null;try{t?(await x.enable(e.name),S.success(`${e.name} enabled.`)):(await x.disable(e.name),S.success(`${e.name} disabled.`)),await D()}catch(n){Tt(t?`Couldn't enable ${e.name}`:`Couldn't disable ${e.name}`,n,`Failed to update plugin.`)}finally{W.value=null}}}let G=r(null);async function Dt(){let e=G.value;if(e)try{await x.uninstall(e.name),S.success(`${e.name} uninstalled.`),G.value=null,await D()}catch(t){Tt(`Couldn't uninstall ${e.name}`,t,`Failed to uninstall plugin.`),G.value=null}}let K=r(null),q=r(null),Ot=r(!1),J=r(!1),Y=r({}),X=r({}),Z=r({}),kt=l(()=>K.value?`Configure — ${K.value.name}`:`Configure plugin`),At=l(()=>q.value?Object.entries(q.value.settings_schema):[]),jt=l(()=>At.value.length>0);function Mt(e){return e.type===`int`||e.type===`integer`||e.type===`number`||e.type===`float`?`number`:`text`}function Nt(e){return e.type===`bool`||e.type===`boolean`}function Pt(e,t){return Nt(e)?t===!0||t===1||t===`1`||t===`true`:e.secret?``:t??(e.default===void 0?``:e.default)}function Q(e){return q.value?.secret_status?.[e]??null}function Ft(e){return`•`.repeat(Math.max(1,Math.min(e,32)))}function It(e){if(e.secret||!(`default`in e))return null;let t=e.default;return t==null||t===``?null:typeof t==`boolean`?t?`on`:`off`:String(t)}function Lt(e){return e.link_text&&e.link_text.trim()!==``?e.link_text:`Where to get this`}async function Rt(e){K.value=e,q.value=null,Y.value={},X.value={},Z.value={},Ot.value=!0;try{let t=await x.get(e.name);q.value=t;let n={};for(let[e,r]of Object.entries(t.settings_schema))n[e]=Pt(r,t.settings[e]);Y.value=n,X.value={...n}}catch(e){S.error(g(e,`Failed to load plugin settings.`)),K.value=null}finally{Ot.value=!1}}function $(){K.value=null,q.value=null,Y.value={},X.value={},Z.value={}}function zt(){let e={};if(!q.value)return e;for(let[t,n]of Object.entries(q.value.settings_schema)){let r=Y.value[t];if(n.secret){if(r===``||r==null)continue;e[t]=r;continue}r!==X.value[t]&&(Nt(n)?e[t]=r===!0:Mt(n)===`number`?e[t]=r===``||r===null?r:Number(r):e[t]=r)}return e}async function Bt(){let e=K.value;if(!e)return;Z.value={};let t=zt();if(Object.keys(t).length===0){S.success(`No changes to save.`),$();return}J.value=!0;try{await x.updateSettings(e.name,t),S.success(`Settings saved.`),$(),await D()}catch(e){let t=me(e);Object.keys(t).length>0?(Z.value=t,S.error(`Some settings could not be saved — check the highlighted fields.`)):S.error(g(e,`Failed to save settings.`))}finally{J.value=!1}}async function Vt(){try{V.value=await x.getAutoUpdate()}catch{}}return n(()=>{et(),E(),Vt()}),(n,r)=>(e(),f(`section`,_e,[d(`header`,ve,[r[12]||=d(`h1`,{id:`plugins-heading`,class:`admin-plugins__title`},`Plugins`,-1),d(`div`,ye,[o(y,{"model-value":V.value,label:`Auto-update`,"aria-label":`Toggle automatic plugin updates`,"onUpdate:modelValue":wt},null,8,[`model-value`]),r[11]||=d(`span`,{class:`admin-plugins__head-spacer`},null,-1),o(_,{variant:`ghost`,size:`sm`,"left-icon":`rewind`,loading:R.value,onClick:bt},{default:m(()=>[...r[8]||=[c(` Check for updates `,-1)]]),_:1},8,[`loading`]),_t.value>0?(e(),a(_,{key:0,variant:`solid`,size:`sm`,"left-icon":`forward`,loading:z.value,onClick:Ct},{default:m(()=>[c(` Update all (`+p(_t.value)+`) `,1)]),_:1},8,[`loading`])):s(``,!0),o(_,{variant:`ghost`,size:`sm`,"left-icon":`plus`,onClick:pt},{default:m(()=>[...r[9]||=[c(`Add catalog`,-1)]]),_:1}),o(_,{variant:`solid`,size:`sm`,"left-icon":`plus`,onClick:st},{default:m(()=>[...r[10]||=[c(`Install from URL`,-1)]]),_:1})])]),o(pe,null,{default:m(()=>[...r[13]||=[c(` Extend Phlix with add-ons from the plugin catalog. On each catalog plugin, toggle its switch to `,-1),d(`strong`,null,`enable/disable`,-1),c(` it, `,-1),d(`strong`,null,`Install`,-1),c(` or `,-1),d(`strong`,null,`Uninstall`,-1),c(` it, `,-1),d(`strong`,null,`Configure`,-1),c(` its settings, or `,-1),d(`strong`,null,`Update`,-1),c(` when a new version is out. Up top, `,-1),d(`strong`,null,`Check for updates`,-1),c(` refreshes availability, `,-1),d(`strong`,null,`Update all`,-1),c(` upgrades everything at once, `,-1),d(`strong`,null,`Auto-update`,-1),c(` keeps plugins current automatically, and `,-1),d(`strong`,null,`Add catalog`,-1),c(` / `,-1),d(`strong`,null,`Install from URL`,-1),c(` pull in sources or plugins from outside the default catalog. `,-1)]]),_:1}),d(`div`,be,[r[14]||=d(`span`,{class:`admin-plugins__sources-label`},`Catalogs`,-1),(e(!0),f(u,null,i(T.value.sources,t=>(e(),a(v,{key:t,tone:`neutral`},{default:m(()=>[d(`a`,{class:`admin-plugins__source-link`,href:t,target:`_blank`,rel:`noopener noreferrer`},p(it(t)),9,xe),t===T.value.default_source?s(``,!0):(e(),f(`button`,{key:0,type:`button`,class:`admin-plugins__source-remove`,disabled:I.value===t,"aria-label":`Remove catalog ${it(t)}`,onClick:e=>gt(t)},` × `,8,Se))]),_:2},1024))),128))]),M.value?(e(),f(`div`,Ce,[o(se,{name:`alert`,class:`admin-plugins__install-error-icon`}),d(`div`,we,[r[15]||=d(`strong`,null,`Couldn't install the plugin.`,-1),d(`span`,null,p(M.value),1)]),d(`button`,{type:`button`,class:`admin-plugins__install-error-dismiss`,"aria-label":`Dismiss`,onClick:r[0]||=e=>M.value=null},` × `)])):s(``,!0),U.value?(e(),f(`div`,Te,[o(se,{name:`alert`,class:`admin-plugins__install-error-icon`}),d(`div`,Ee,[d(`strong`,null,p(U.value.title)+`.`,1),d(`span`,null,p(U.value.message),1)]),d(`button`,{type:`button`,class:`admin-plugins__install-error-dismiss`,"aria-label":`Dismiss`,onClick:r[1]||=e=>U.value=null},` × `)])):s(``,!0),tt.value?(e(),f(`div`,De,[o(de,{variant:`text`,lines:5})])):(e(),f(u,{key:3},[(e(!0),f(u,null,i(T.value.errors,t=>(e(),f(`p`,{key:t.source,class:`admin-plugins__catalog-error`,role:`alert`},[r[16]||=c(` Couldn't load catalog `,-1),d(`strong`,null,p(it(t.source)),1),c(` — `+p(t.error),1)]))),128)),O.value.length===0&&T.value.errors.length===0?(e(),a(fe,{key:0,icon:`settings`,title:`No plugins in the catalog`,description:`Add a catalog source or install a plugin directly from its URL.`},{actions:m(()=>[o(_,{variant:`solid`,size:`sm`,"left-icon":`plus`,onClick:st},{default:m(()=>[...r[17]||=[c(`Install from URL`,-1)]]),_:1})]),_:1})):(e(),f(`ul`,Oe,[(e(!0),f(u,null,i(O.value,t=>(e(),f(`li`,{key:t.name,class:`admin-plugins__card`},[d(`div`,ke,[d(`h3`,Ae,p(t.title),1),d(`div`,je,[t.type?(e(),a(v,{key:0,tone:`info`},{default:m(()=>[c(p(t.type),1)]),_:2},1024)):s(``,!0),t.installed?(e(),a(v,{key:1,tone:`success`},{default:m(()=>[...r[18]||=[c(`Installed`,-1)]]),_:1})):s(``,!0),H(t.name)?(e(),a(v,{key:2,tone:`warning`,class:`admin-plugins__update-badge`},{default:m(()=>[c(` Update → v`+p(H(t.name)?.latest_version),1)]),_:2},1024)):s(``,!0)])]),t.summary||t.description?(e(),f(`p`,Me,p(t.summary||t.description),1)):s(``,!0),t.tags.length?(e(),f(`div`,Ne,[(e(!0),f(u,null,i(t.tags,t=>(e(),f(`span`,{key:t,class:`admin-plugins__tag`},p(t),1))),128))])):s(``,!0),d(`div`,Pe,[t.installed?(e(),f(u,{key:0},[o(y,{"model-value":t.enabled,label:t.enabled?`Enabled`:`Disabled`,"aria-label":`Toggle ${t.title}`,disabled:W.value===t.name,"onUpdate:modelValue":e=>Et(k(t),e)},null,8,[`model-value`,`label`,`aria-label`,`disabled`,`onUpdate:modelValue`]),r[22]||=d(`span`,{class:`admin-plugins__card-spacer`},null,-1),H(t.name)?(e(),a(_,{key:0,variant:`solid`,size:`sm`,"left-icon":`forward`,loading:B.value===t.name,"aria-label":`Update ${t.title}`,onClick:e=>St(k(t))},{default:m(()=>[...r[19]||=[c(` Update `,-1)]]),_:1},8,[`loading`,`aria-label`,`onClick`])):s(``,!0),o(_,{variant:`ghost`,size:`sm`,"aria-label":`Configure ${t.title}`,onClick:e=>Rt(k(t))},{default:m(()=>[...r[20]||=[c(` Configure `,-1)]]),_:1},8,[`aria-label`,`onClick`]),o(_,{variant:`ghost`,size:`sm`,"aria-label":`Uninstall ${t.title}`,onClick:e=>G.value=k(t)},{default:m(()=>[...r[21]||=[c(` Uninstall `,-1)]]),_:1},8,[`aria-label`,`onClick`])],64)):(e(),f(u,{key:1},[o(_,{variant:`solid`,size:`sm`,"left-icon":`plus`,loading:N.value===t.repo,"aria-label":`Install ${t.title}`,onClick:e=>dt(t)},{default:m(()=>[...r[23]||=[c(` Install `,-1)]]),_:1},8,[`loading`,`aria-label`,`onClick`]),r[24]||=d(`span`,{class:`admin-plugins__card-spacer`},null,-1),d(`a`,{class:`admin-plugins__repo-link`,href:t.repo,target:`_blank`,rel:`noopener noreferrer`},`Repo ↗`,8,Fe)],64))])]))),128))]))],64)),rt.value.length?(e(),f(`section`,Ie,[r[29]||=d(`h2`,{id:`orphans-heading`,class:`admin-plugins__subtitle`},`Other installed plugins`,-1),r[30]||=d(`p`,{class:`admin-plugins__subnote`},`Installed directly from a URL and not listed in any catalog.`,-1),d(`table`,Le,[r[28]||=d(`thead`,null,[d(`tr`,null,[d(`th`,{scope:`col`},`Name`),d(`th`,{scope:`col`},`Version`),d(`th`,{scope:`col`},`Type`),d(`th`,{scope:`col`},`Enabled`),d(`th`,{scope:`col`,class:`admin-plugins__actions-col`},`Actions`)])],-1),d(`tbody`,null,[(e(!0),f(u,null,i(rt.value,t=>(e(),f(`tr`,{key:t.name},[d(`td`,null,p(t.name),1),d(`td`,Re,[c(p(t.version)+` `,1),H(t.name)?(e(),a(v,{key:0,tone:`warning`,class:`admin-plugins__update-badge`},{default:m(()=>[c(` → v`+p(H(t.name)?.latest_version),1)]),_:2},1024)):s(``,!0)]),d(`td`,null,[o(v,{tone:`info`},{default:m(()=>[c(p(t.type),1)]),_:2},1024)]),d(`td`,null,[o(y,{"model-value":t.enabled,label:t.enabled?`Enabled`:`Disabled`,"aria-label":`Toggle ${t.name}`,disabled:W.value===t.name,"onUpdate:modelValue":e=>Et(t,e)},null,8,[`model-value`,`label`,`aria-label`,`disabled`,`onUpdate:modelValue`])]),d(`td`,null,[d(`div`,ze,[H(t.name)?(e(),a(_,{key:0,variant:`solid`,size:`sm`,"left-icon":`forward`,loading:B.value===t.name,"aria-label":`Update ${t.name}`,onClick:e=>St(t)},{default:m(()=>[...r[25]||=[c(` Update `,-1)]]),_:1},8,[`loading`,`aria-label`,`onClick`])):s(``,!0),o(_,{variant:`ghost`,size:`sm`,"aria-label":`Configure ${t.name}`,onClick:e=>Rt(t)},{default:m(()=>[...r[26]||=[c(` Configure `,-1)]]),_:1},8,[`aria-label`,`onClick`]),o(_,{variant:`ghost`,size:`sm`,"aria-label":`Uninstall ${t.name}`,onClick:e=>G.value=t},{default:m(()=>[...r[27]||=[c(` Uninstall `,-1)]]),_:1},8,[`aria-label`,`onClick`])])])]))),128))])])])):s(``,!0),w.value&&!C.value?(e(),a(fe,{key:5,icon:`alert`,title:`Couldn't load installed plugins`,description:w.value},{actions:m(()=>[o(_,{variant:`solid`,size:`sm`,"left-icon":`rewind`,onClick:et},{default:m(()=>[...r[31]||=[c(`Retry`,-1)]]),_:1})]),_:1},8,[`description`])):s(``,!0),o(h,{modelValue:A.value,"onUpdate:modelValue":r[3]||=e=>A.value=e,title:`Install from URL`,onClose:ct},{footer:m(()=>[o(_,{variant:`ghost`,size:`sm`,onClick:ct},{default:m(()=>[...r[34]||=[c(`Cancel`,-1)]]),_:1}),o(_,{variant:`solid`,size:`sm`,loading:at.value,onClick:ut},{default:m(()=>[...r[35]||=[c(`Install`,-1)]]),_:1},8,[`loading`])]),default:m(()=>[d(`form`,{class:`admin-plugins__form`,onSubmit:ae(ut,[`prevent`])},[d(`label`,Be,[r[32]||=d(`span`,{class:`admin-plugins__label`},`Plugin URL`,-1),t(d(`input`,{"onUpdate:modelValue":r[2]||=e=>j.value=e,type:`url`,class:`admin-plugins__input`,autocomplete:`off`,placeholder:`https://github.com/owner/phlix-plugin-name`,required:``},null,512),[[re,j.value]]),r[33]||=d(`span`,{class:`admin-plugins__hint`},` A plugin archive or git repository URL to download and install. `,-1)])],32)]),_:1},8,[`modelValue`]),o(h,{modelValue:P.value,"onUpdate:modelValue":r[5]||=e=>P.value=e,title:`Add catalog`,onClose:mt},{footer:m(()=>[o(_,{variant:`ghost`,size:`sm`,onClick:mt},{default:m(()=>[...r[38]||=[c(`Cancel`,-1)]]),_:1}),o(_,{variant:`solid`,size:`sm`,loading:ft.value,onClick:ht},{default:m(()=>[...r[39]||=[c(`Add`,-1)]]),_:1},8,[`loading`])]),default:m(()=>[d(`form`,{class:`admin-plugins__form`,onSubmit:ae(ht,[`prevent`])},[d(`label`,Ve,[r[36]||=d(`span`,{class:`admin-plugins__label`},`Catalog URL`,-1),t(d(`input`,{"onUpdate:modelValue":r[4]||=e=>F.value=e,type:`url`,class:`admin-plugins__input`,autocomplete:`off`,placeholder:`https://github.com/owner/phlix-plugins`,required:``},null,512),[[re,F.value]]),r[37]||=d(`span`,{class:`admin-plugins__hint`},[c(` A repository (or direct `),d(`code`,null,`plugins.json`),c(` URL) listing installable plugins. `)],-1)])],32)]),_:1},8,[`modelValue`]),o(h,{"model-value":G.value!==null,title:`Uninstall plugin`,size:`sm`,"onUpdate:modelValue":r[7]||=e=>G.value=null},{footer:m(()=>[o(_,{variant:`ghost`,size:`sm`,onClick:r[6]||=e=>G.value=null},{default:m(()=>[...r[42]||=[c(`Cancel`,-1)]]),_:1}),o(_,{variant:`solid`,size:`sm`,onClick:Dt},{default:m(()=>[...r[43]||=[c(`Uninstall`,-1)]]),_:1})]),default:m(()=>[d(`p`,null,[r[40]||=c(` Uninstall `,-1),d(`strong`,null,p(G.value?.name),1),r[41]||=c(`? This removes the plugin and its settings and cannot be undone. `,-1)])]),_:1},8,[`model-value`]),o(h,{"model-value":K.value!==null,title:kt.value,size:`lg`,"onUpdate:modelValue":$},{footer:m(()=>[o(_,{variant:`ghost`,size:`sm`,onClick:$},{default:m(()=>[...r[45]||=[c(`Cancel`,-1)]]),_:1}),jt.value?(e(),a(_,{key:0,variant:`solid`,size:`sm`,loading:J.value,onClick:Bt},{default:m(()=>[...r[46]||=[c(` Save `,-1)]]),_:1},8,[`loading`])):s(``,!0)]),default:m(()=>[Ot.value?(e(),f(`div`,He,[o(de,{variant:`text`,lines:4})])):(e(),f(u,{key:1},[jt.value?(e(),f(`form`,{key:1,class:`admin-plugins__form`,onSubmit:ae(Bt,[`prevent`])},[(e(!0),f(u,null,i(At.value,([n,i])=>(e(),f(`div`,{key:n,class:`admin-plugins__field`},[i.type===`bool`||i.type===`boolean`?(e(),a(y,{key:0,"model-value":Y.value[n]===!0,label:i.label||n,"onUpdate:modelValue":e=>Y.value[n]=e},null,8,[`model-value`,`label`,`onUpdate:modelValue`])):(e(),f(u,{key:1},[d(`span`,Ue,[c(p(i.label||n)+` `,1),i.required?(e(),f(`span`,We,`*`)):(e(),f(`span`,Ge,`optional`))]),t(d(`input`,{"onUpdate:modelValue":e=>Y.value[n]=e,type:i.secret?`password`:Mt(i),class:ne([`admin-plugins__input`,{"is-invalid":Z.value[n]}]),autocomplete:i.secret?`new-password`:`off`,placeholder:i.secret?Q(n)?.set?`Leave blank to keep the current value`:`Not set — enter a value`:void 0,"aria-label":i.label||n,"aria-invalid":Z.value[n]?`true`:void 0},null,10,Ke),[[ie,Y.value[n]]]),i.secret?(e(),f(`span`,{key:0,class:ne([`admin-plugins__secret-status`,{"is-set":Q(n)?.set}])},[Q(n)?.set?(e(),f(u,{key:0},[d(`span`,qe,p(Ft(Q(n)?.length??0)),1),c(` Currently set (`+p(Q(n)?.length)+` characters) — leave blank to keep it. `,1)],64)):(e(),f(u,{key:1},[c(`Not set.`)],64))],2)):s(``,!0)],64)),i.description?(e(),f(`span`,Je,p(i.description),1)):s(``,!0),It(i)?(e(),f(`span`,Ye,[r[44]||=c(` Default: `,-1),d(`code`,null,p(It(i)),1)])):s(``,!0),i.link?(e(),f(`a`,{key:4,class:`admin-plugins__field-link`,href:i.link,target:`_blank`,rel:`noopener noreferrer`},p(Lt(i))+` ↗ `,9,Xe)):s(``,!0),Z.value[n]?(e(),f(`span`,Ze,p(Z.value[n]),1)):s(``,!0)]))),128))],32)):(e(),a(fe,{key:0,icon:`settings`,title:`No configurable settings`,description:`This plugin does not expose any settings.`}))],64))]),_:1},8,[`model-value`,`title`])]))}}),[[`__scopeId`,`data-v-bddf3d8e`]]);export{b as default};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unsafe field-help link schemes

When an installed plugin's manifest declares a settings help link such as javascript:..., the configure modal now renders that value directly as an <a href>; the server passes manifest/overlay link strings through unchanged (SettingsMasker::schema() / PluginFieldHelp::decorate()), so clicking the help link can execute script instead of navigating to documentation. Please restrict these links to safe schemes (for example https:/http:) before rendering or before returning them from the API.

Useful? React with 👍 / 👎.

@detain
detain merged commit 4ef38c1 into master Jul 11, 2026
14 of 16 checks passed
@detain
detain deleted the chore/spa-bundle-0.79.0 branch July 11, 2026 01:26
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.35%. Comparing base (3c4da60) to head (2a99a4a).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #524      +/-   ##
============================================
+ Coverage     57.22%   57.35%   +0.13%     
  Complexity    17495    17495              
============================================
  Files           587      587              
  Lines         54893    54893              
============================================
+ Hits          31410    31485      +75     
+ Misses        23483    23408      -75     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

detain added a commit that referenced this pull request Jul 12, 2026
…524)

Bumps web-ui's @phlix/ui pin v0.78.0 -> v0.79.0 and regenerates the committed
public/assets/app/ bundle, so the deployed server actually serves the plugin
configure/lifecycle UX (visible enable errors, secret set/length status,
field-help links). Version 1.2.1 -> 1.2.2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant