adminizer 5.1.0-build.26 → 5.1.0-build.28
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/assets/{abnfDiagram-VCTEODGH-BPk_d6eP.js → abnfDiagram-VCTEODGH-uWOVpoSR.js} +1 -1
- package/assets/add-A_fIlNKI.js +1 -0
- package/assets/{add-group-Dgnx2vMh.js → add-group-C2x6K3tV.js} +1 -1
- package/assets/{add-user-BfR1nhGQ.js → add-user-u61kYlEm.js} +1 -1
- package/assets/ai-assistant/agent.es.js +7894 -6950
- package/assets/app-CRa5Kokq.js +138 -0
- package/assets/app-TCCHARSx.css +1 -0
- package/assets/{arc-uDJSz8bh.js → arc--ZX64bmY.js} +1 -1
- package/assets/{architectureDiagram-5GKGNRK7-OIR43cRx.js → architectureDiagram-5GKGNRK7-W9AzLZvl.js} +1 -1
- package/assets/{blockDiagram-I7D4REHJ-h6ySQ0QE.js → blockDiagram-I7D4REHJ-BpK_yEa7.js} +1 -1
- package/assets/{c4Diagram-7LVT6UL2-DOT80T2g.js → c4Diagram-7LVT6UL2-DiQzU1s0.js} +1 -1
- package/assets/{catalog-B6ZGmhng.js → catalog-dSCv3Szm.js} +3 -3
- package/assets/channel-DRO88SVn.js +1 -0
- package/assets/{chunk-2Q5K7J3B-BoyYAiVR.js → chunk-2Q5K7J3B-GPHETbCq.js} +1 -1
- package/assets/{chunk-5VM5RSS4-B-ZfcfTz.js → chunk-5VM5RSS4-bLovkogd.js} +1 -1
- package/assets/{chunk-F27PBJKO-GsDMG_ue.js → chunk-F27PBJKO-DbkTY41z.js} +1 -1
- package/assets/{chunk-IMKFNOWR-zvoLOzaf.js → chunk-IMKFNOWR-CL0VAxX7.js} +1 -1
- package/assets/{chunk-JWPE2WC7-DYtJ3ly7.js → chunk-JWPE2WC7-d1Grk14b.js} +1 -1
- package/assets/{chunk-POPQ4Y6H-ChVoBm_0.js → chunk-POPQ4Y6H-CAUhiiNt.js} +1 -1
- package/assets/{chunk-SVP7TREG-BV0LTCGr.js → chunk-SVP7TREG-C8ADZUEr.js} +1 -1
- package/assets/{chunk-TICWLB2K-Dmp4mCPP.js → chunk-TICWLB2K-oRqFZabo.js} +1 -1
- package/assets/{chunk-XXDRQBXY-d7y3nWm0.js → chunk-XXDRQBXY-C3zo4c2P.js} +1 -1
- package/assets/classDiagram-ZZMXUADV-Cb-leHEm.js +1 -0
- package/assets/classDiagram-v2-VYDZK3BY-Cb-leHEm.js +1 -0
- package/assets/controls/ckeditor.es.js +265 -259
- package/assets/controls/jsoneditor.es.js +5921 -5896
- package/assets/controls/leaflet.es.js +29 -26
- package/assets/{cose-bilkent-JH36ORCC-DfL41_IE.js → cose-bilkent-JH36ORCC-ZPvVaI6n.js} +1 -1
- package/assets/{cynefin-OW5HDTMX-CdMB9V74.js → cynefin-OW5HDTMX-EEL4eqU7.js} +1 -1
- package/assets/{cynefinDiagram-5FMLGOSQ-B65Z6Wy5.js → cynefinDiagram-5FMLGOSQ-DpDFbbrn.js} +1 -1
- package/assets/{dagre-GXQ25YYZ-vs7dR1k0.js → dagre-GXQ25YYZ-Dtjy1xN1.js} +1 -1
- package/assets/{dashboard-gVK2jkxM.js → dashboard-DXs-vdJG.js} +2 -2
- package/assets/{diagram-S7CK7UJ4-BP2KlMF7.js → diagram-S7CK7UJ4-BhWisCOy.js} +1 -1
- package/assets/{diagram-UQ7AKVKN-DtBR3nL4.js → diagram-UQ7AKVKN-BBH2Mm33.js} +1 -1
- package/assets/{diagram-VSXAHHWV-DNqepAMp.js → diagram-VSXAHHWV-B1Xc2cPz.js} +1 -1
- package/assets/{diagram-VX7I27RA-B_qeXnMo.js → diagram-VX7I27RA-CTTYzy1s.js} +1 -1
- package/assets/{diagram-Z3DM3KII-Deat3YWi.js → diagram-Z3DM3KII-DdH0yWH0.js} +1 -1
- package/assets/{docs-CV8mkNUq.js → docs-BZYlfOON.js} +1 -1
- package/assets/{ebnfDiagram-PWID7BFC-C0qShrVz.js → ebnfDiagram-PWID7BFC-R9cSqf0y.js} +1 -1
- package/assets/{erDiagram-RLTQ6QDP-ByGzKaF4.js → erDiagram-RLTQ6QDP-Bpu-Pm95.js} +1 -1
- package/assets/{flowDiagram-HODETNUW-DP7cFly4.js → flowDiagram-HODETNUW-NdXf-fRN.js} +1 -1
- package/assets/{ganttDiagram-EL5Y4UJY-CxsNBze0.js → ganttDiagram-EL5Y4UJY-itQHv_dH.js} +1 -1
- package/assets/{gitGraphDiagram-WWUBYQGX-DAjOquZ3.js → gitGraphDiagram-WWUBYQGX-DorHK4Wx.js} +1 -1
- package/assets/{history-BjZubBIS.js → history-DB48MCa8.js} +1 -1
- package/assets/{infoDiagram-27XIBGKW-DAHcs_EB.js → infoDiagram-27XIBGKW-FgFATKk4.js} +1 -1
- package/assets/{init-user-Y2cS_Umn.js → init-user-DAXb55S_.js} +1 -1
- package/assets/{ishikawaDiagram-5VMMS53U-DG-NixrB.js → ishikawaDiagram-5VMMS53U-Bjl1a496.js} +1 -1
- package/assets/{journeyDiagram-3NMN7TZE-B3QkLsRE.js → journeyDiagram-3NMN7TZE-DU1TmnbI.js} +1 -1
- package/assets/{kanban-definition-UXKFOSKX-Co9SxVeD.js → kanban-definition-UXKFOSKX-BsrlKdcU.js} +1 -1
- package/assets/{layout-CcAsMdrw.js → layout-C68duuC7.js} +1 -1
- package/assets/{linear-p2QT3a1B.js → linear-DdadsTLS.js} +1 -1
- package/assets/{list-Bu_Q0EVP.js → list-D3R3q3Ti.js} +2 -2
- package/assets/{login-n9fNk99k.js → login-DwxC2WD6.js} +1 -1
- package/assets/manifest.json +277 -277
- package/assets/{media-manager-ChlwzfP8.js → media-manager-mt4Ykki7.js} +1 -1
- package/assets/{mermaid.core-CUYdfyhY.js → mermaid.core-DxLHD4PF.js} +5 -5
- package/assets/{mindmap-definition-YA3MSWOX-9_Osk887.js → mindmap-definition-YA3MSWOX-BfdP8hOy.js} +1 -1
- package/assets/module-DKdDlng0.js +1 -0
- package/assets/{notification-DTOo0s9p.js → notification-DDWbaooT.js} +1 -1
- package/assets/{pegDiagram-XKGWAZYB-xGo27v2q.js → pegDiagram-XKGWAZYB-BO9cFGCI.js} +1 -1
- package/assets/{pieDiagram-E7YTZNPT-BlxVq7jD.js → pieDiagram-E7YTZNPT-B4KrTpto.js} +1 -1
- package/assets/{quadrantDiagram-AXDQQJYC-DSIsRL3O.js → quadrantDiagram-AXDQQJYC-BumScZvv.js} +1 -1
- package/assets/{railroadDiagram-O6MQD6OU-CcXubifR.js → railroadDiagram-O6MQD6OU-DUDQQC-p.js} +1 -1
- package/assets/{register-sc1NPrUn.js → register-5rs1OSaq.js} +1 -1
- package/assets/{requirementDiagram-BXWQKSXE-CQ0HxjT5.js → requirementDiagram-BXWQKSXE-BtNsxNt1.js} +1 -1
- package/assets/{ru-BcGQTZye.js → ru-Cf1WWSt0.js} +1 -1
- package/assets/{sankeyDiagram-P5KCCOFB-BfeizISp.js → sankeyDiagram-P5KCCOFB-xjPcSTcN.js} +1 -1
- package/assets/{sequenceDiagram-WJ2MYXX4-U6sVYSC5.js → sequenceDiagram-WJ2MYXX4-BVOgA3-B.js} +1 -1
- package/assets/{sizeCapture-INFHLROL-sy73XuOR.js → sizeCapture-INFHLROL-BDI5FsKO.js} +1 -1
- package/assets/{stateDiagram-D77RDMKH-BBQ4V6V5.js → stateDiagram-D77RDMKH-DreIJby8.js} +1 -1
- package/assets/stateDiagram-v2-MP3YSRHH-BNzSJJYe.js +1 -0
- package/assets/{swimlanes-42K2YHIH-DgNoaSy5.js → swimlanes-42K2YHIH-J_CNnPZv.js} +1 -1
- package/assets/swimlanesDiagram-VR7AAH4N-DN5N1rG7.js +8 -0
- package/assets/{timeline-definition-24CTP7MA-DCZ7_pVf.js → timeline-definition-24CTP7MA-BXGN0YmT.js} +1 -1
- package/assets/{use-appearance-B1Ulba0z.js → use-appearance-B2YFalXG.js} +1 -1
- package/assets/{user-filters-list-Jd8ADfF3.js → user-filters-list-D8WohjvX.js} +1 -1
- package/assets/{vennDiagram-4TSXK5OY-BH1b3-en.js → vennDiagram-4TSXK5OY-Mu0lMKUo.js} +1 -1
- package/assets/{wardleyDiagram-VM6X3IG4-4tU49eCf.js → wardleyDiagram-VM6X3IG4-CXFMI74r.js} +1 -1
- package/assets/{welcome-CoEo_2b6.js → welcome-DHQx7R11.js} +1 -1
- package/assets/with-app-layout-Cvg36gxr.js +31 -0
- package/assets/{xychartDiagram-S5SC5T6Z-C-jB1QUO.js → xychartDiagram-S5SC5T6Z-DCr7j1YG.js} +1 -1
- package/controllers/adminLinks.d.ts +7 -0
- package/controllers/adminLinks.js +10 -0
- package/helpers/menuHelper.d.ts +11 -1
- package/helpers/menuHelper.js +16 -6
- package/helpers/navigationAccessHelper.d.ts +10 -3
- package/helpers/navigationAccessHelper.js +21 -2
- package/helpers/pathPattern.d.ts +5 -0
- package/helpers/pathPattern.js +10 -0
- package/interfaces/adminpanelConfig.d.ts +23 -0
- package/lib/admin-links/AdminLinkHandler.d.ts +41 -0
- package/lib/admin-links/AdminLinkHandler.js +94 -1
- package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +4 -22
- package/lib/ai-assistant/AiAssistantUiMethodHandler.js +3 -47
- package/lib/docs/AbstractDocumentation.js +2 -4
- package/package.json +1 -2
- package/system/Router.js +7 -0
- package/system/bindInertia.js +16 -0
- package/ui/components/VanillaJSONEditor.js +50 -11
- package/ui/components/ui/badge.d.ts +1 -1
- package/ui/components/ui/badge.js +1 -0
- package/ui/components/ui/command.d.ts +3 -1
- package/ui/components/ui/command.js +2 -2
- package/assets/add-BJKSs5TK.js +0 -1
- package/assets/app-BKQr0JOg.js +0 -138
- package/assets/app-DPmI-MvN.css +0 -1
- package/assets/channel-e6rquzsE.js +0 -1
- package/assets/classDiagram-ZZMXUADV-DIbGlLDx.js +0 -1
- package/assets/classDiagram-v2-VYDZK3BY-DIbGlLDx.js +0 -1
- package/assets/module-jjWCUszz.js +0 -1
- package/assets/stateDiagram-v2-MP3YSRHH-BrVvb-bz.js +0 -1
- package/assets/swimlanesDiagram-VR7AAH4N-CkLcfj4e.js +0 -8
- package/assets/with-app-layout-R5blfHwR.js +0 -31
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{s as pi,g as mi,o as $t,n as yi,a as bi,b as wi,_ as a,l as Ut,D as Ai,d as Ci,p as Si,y as Dt,i as Ri,A as Gt,B as _i,E as ki,aC as Ti,a9 as Xt}from"./mermaid.core-
|
|
1
|
+
import{s as pi,g as mi,o as $t,n as yi,a as bi,b as wi,_ as a,l as Ut,D as Ai,d as Ci,p as Si,y as Dt,i as Ri,A as Gt,B as _i,E as ki,aC as Ti,a9 as Xt}from"./mermaid.core-DxLHD4PF.js";import{i as Di}from"./init-Gi6I4Gst.js";import{o as Pi}from"./ordinal-Cboi1Yqb.js";import{l as Nt}from"./linear-DdadsTLS.js";import"./app-CRa5Kokq.js";import"./isObject-DL-BuaBl.js";import"./defaultLocale-DX6XiGOO.js";function vi(e,t,i){e=+e,t=+t,i=(n=arguments.length)<2?(t=e,e=0,1):n<3?1:+i;for(var s=-1,n=Math.max(0,Math.ceil((t-e)/i))|0,o=new Array(n);++s<n;)o[s]=e+s*i;return o}function St(){var e=Pi().unknown(void 0),t=e.domain,i=e.range,s=0,n=1,o,h,p=!1,u=0,f=0,R=.5;delete e.unknown;function C(){var y=t().length,E=n<s,v=E?n:s,L=E?s:n;o=(L-v)/Math.max(1,y-u+f*2),p&&(o=Math.floor(o)),v+=(L-v-o*(y-u))*R,h=o*(1-u),p&&(v=Math.round(v),h=Math.round(h));var M=vi(y).map(function(b){return v+o*b});return i(E?M.reverse():M)}return e.domain=function(y){return arguments.length?(t(y),C()):t()},e.range=function(y){return arguments.length?([s,n]=y,s=+s,n=+n,C()):[s,n]},e.rangeRound=function(y){return[s,n]=y,s=+s,n=+n,p=!0,C()},e.bandwidth=function(){return h},e.step=function(){return o},e.round=function(y){return arguments.length?(p=!!y,C()):p},e.padding=function(y){return arguments.length?(u=Math.min(1,f=+y),C()):u},e.paddingInner=function(y){return arguments.length?(u=Math.min(1,y),C()):u},e.paddingOuter=function(y){return arguments.length?(f=+y,C()):f},e.align=function(y){return arguments.length?(R=Math.max(0,Math.min(1,y)),C()):R},e.copy=function(){return St(t(),[s,n]).round(p).paddingInner(u).paddingOuter(f).align(R)},Di.apply(C(),arguments)}var Rt=(function(){var e=a(function(W,l,g,d){for(g=g||{},d=W.length;d--;g[W[d]]=l);return g},"o"),t=[1,10,12,14,16,18,19,21,23],i=[2,6],s=[1,3],n=[1,5],o=[1,6],h=[1,7],p=[1,5,10,12,14,16,18,19,21,23,36,37,38],u=[1,25],f=[1,26],R=[1,28],C=[1,29],y=[1,30],E=[1,31],v=[1,32],L=[1,33],M=[1,34],b=[1,35],D=[1,36],c=[1,37],z=[1,43],F=[1,42],X=[1,47],N=[1,50],k=[1,10,12,14,16,18,19,21,23,36,37,38],H=[1,10,12,14,16,18,19,21,23,24,26,28,29,36,37,38],A=[1,10,12,14,16,18,19,21,23,24,26,28,29,36,37,38,42,43,44,45,46,47,48,49,50,51],S=[1,65],O=[26,28],T={trace:a(function(){},"trace"),yy:{},symbols_:{error:2,start:3,eol:4,XYCHART:5,chartConfig:6,document:7,CHART_ORIENTATION:8,statement:9,title:10,text:11,X_AXIS:12,parseXAxis:13,Y_AXIS:14,parseYAxis:15,LINE:16,plotData:17,BAR:18,acc_title:19,acc_title_value:20,acc_descr:21,acc_descr_value:22,acc_descr_multiline_value:23,SQUARE_BRACES_START:24,dataPoints:25,SQUARE_BRACES_END:26,dataPoint:27,COMMA:28,NUMBER_WITH_DECIMAL:29,STR:30,xAxisData:31,bandData:32,ARROW_DELIMITER:33,commaSeparatedTexts:34,yAxisData:35,NEWLINE:36,SEMI:37,EOF:38,alphaNum:39,MD_STR:40,alphaNumToken:41,AMP:42,NUM:43,ALPHA:44,PLUS:45,EQUALS:46,MULT:47,DOT:48,BRKT:49,MINUS:50,UNDERSCORE:51,$accept:0,$end:1},terminals_:{2:"error",5:"XYCHART",8:"CHART_ORIENTATION",10:"title",12:"X_AXIS",14:"Y_AXIS",16:"LINE",18:"BAR",19:"acc_title",20:"acc_title_value",21:"acc_descr",22:"acc_descr_value",23:"acc_descr_multiline_value",24:"SQUARE_BRACES_START",26:"SQUARE_BRACES_END",28:"COMMA",29:"NUMBER_WITH_DECIMAL",30:"STR",33:"ARROW_DELIMITER",36:"NEWLINE",37:"SEMI",38:"EOF",40:"MD_STR",42:"AMP",43:"NUM",44:"ALPHA",45:"PLUS",46:"EQUALS",47:"MULT",48:"DOT",49:"BRKT",50:"MINUS",51:"UNDERSCORE"},productions_:[0,[3,2],[3,3],[3,2],[3,1],[6,1],[7,0],[7,2],[9,2],[9,2],[9,2],[9,2],[9,2],[9,3],[9,2],[9,3],[9,2],[9,2],[9,1],[17,3],[25,3],[25,1],[27,2],[27,1],[13,1],[13,2],[13,1],[31,1],[31,3],[32,3],[34,3],[34,1],[15,1],[15,2],[15,1],[35,3],[4,1],[4,1],[4,1],[11,1],[11,1],[11,1],[39,1],[39,2],[41,1],[41,1],[41,1],[41,1],[41,1],[41,1],[41,1],[41,1],[41,1],[41,1]],performAction:a(function(l,g,d,x,_,r,lt){var m=r.length-1;switch(_){case 5:x.setOrientation(r[m]);break;case 9:x.setDiagramTitle(r[m].text.trim());break;case 12:x.setLineData({text:"",type:"text"},r[m]);break;case 13:x.setLineData(r[m-1],r[m]);break;case 14:x.setBarData({text:"",type:"text"},r[m]);break;case 15:x.setBarData(r[m-1],r[m]);break;case 16:this.$=r[m].trim(),x.setAccTitle(this.$);break;case 17:case 18:this.$=r[m].trim(),x.setAccDescription(this.$);break;case 19:this.$=r[m-1];break;case 20:case 30:this.$=[r[m-2],...r[m]];break;case 21:case 31:this.$=[r[m]];break;case 22:this.$={value:Number(r[m-1]),label:r[m]};break;case 23:this.$={value:Number(r[m]),label:""};break;case 24:x.setXAxisTitle(r[m]);break;case 25:x.setXAxisTitle(r[m-1]);break;case 26:x.setXAxisTitle({type:"text",text:""});break;case 27:x.setXAxisBand(r[m]);break;case 28:x.setXAxisRangeData(Number(r[m-2]),Number(r[m]));break;case 29:this.$=r[m-1];break;case 32:x.setYAxisTitle(r[m]);break;case 33:x.setYAxisTitle(r[m-1]);break;case 34:x.setYAxisTitle({type:"text",text:""});break;case 35:x.setYAxisRangeData(Number(r[m-2]),Number(r[m]));break;case 39:this.$={text:r[m],type:"text"};break;case 40:this.$={text:r[m],type:"text"};break;case 41:this.$={text:r[m],type:"markdown"};break;case 42:this.$=r[m];break;case 43:this.$=r[m-1]+""+r[m];break}},"anonymous"),table:[e(t,i,{3:1,4:2,7:4,5:s,36:n,37:o,38:h}),{1:[3]},e(t,i,{4:2,7:4,3:8,5:s,36:n,37:o,38:h}),e(t,i,{4:2,7:4,6:9,3:10,5:s,8:[1,11],36:n,37:o,38:h}),{1:[2,4],9:12,10:[1,13],12:[1,14],14:[1,15],16:[1,16],18:[1,17],19:[1,18],21:[1,19],23:[1,20]},e(p,[2,36]),e(p,[2,37]),e(p,[2,38]),{1:[2,1]},e(t,i,{4:2,7:4,3:21,5:s,36:n,37:o,38:h}),{1:[2,3]},e(p,[2,5]),e(t,[2,7],{4:22,36:n,37:o,38:h}),{11:23,30:u,39:24,40:f,41:27,42:R,43:C,44:y,45:E,46:v,47:L,48:M,49:b,50:D,51:c},{11:39,13:38,24:z,29:F,30:u,31:40,32:41,39:24,40:f,41:27,42:R,43:C,44:y,45:E,46:v,47:L,48:M,49:b,50:D,51:c},{11:45,15:44,29:X,30:u,35:46,39:24,40:f,41:27,42:R,43:C,44:y,45:E,46:v,47:L,48:M,49:b,50:D,51:c},{11:49,17:48,24:N,30:u,39:24,40:f,41:27,42:R,43:C,44:y,45:E,46:v,47:L,48:M,49:b,50:D,51:c},{11:52,17:51,24:N,30:u,39:24,40:f,41:27,42:R,43:C,44:y,45:E,46:v,47:L,48:M,49:b,50:D,51:c},{20:[1,53]},{22:[1,54]},e(k,[2,18]),{1:[2,2]},e(k,[2,8]),e(k,[2,9]),e(H,[2,39],{41:55,42:R,43:C,44:y,45:E,46:v,47:L,48:M,49:b,50:D,51:c}),e(H,[2,40]),e(H,[2,41]),e(A,[2,42]),e(A,[2,44]),e(A,[2,45]),e(A,[2,46]),e(A,[2,47]),e(A,[2,48]),e(A,[2,49]),e(A,[2,50]),e(A,[2,51]),e(A,[2,52]),e(A,[2,53]),e(k,[2,10]),e(k,[2,24],{32:41,31:56,24:z,29:F}),e(k,[2,26]),e(k,[2,27]),{33:[1,57]},{11:59,30:u,34:58,39:24,40:f,41:27,42:R,43:C,44:y,45:E,46:v,47:L,48:M,49:b,50:D,51:c},e(k,[2,11]),e(k,[2,32],{35:60,29:X}),e(k,[2,34]),{33:[1,61]},e(k,[2,12]),{17:62,24:N},{25:63,27:64,29:S},e(k,[2,14]),{17:66,24:N},e(k,[2,16]),e(k,[2,17]),e(A,[2,43]),e(k,[2,25]),{29:[1,67]},{26:[1,68]},{26:[2,31],28:[1,69]},e(k,[2,33]),{29:[1,70]},e(k,[2,13]),{26:[1,71]},{26:[2,21],28:[1,72]},e(O,[2,23],{30:[1,73]}),e(k,[2,15]),e(k,[2,28]),e(k,[2,29]),{11:59,30:u,34:74,39:24,40:f,41:27,42:R,43:C,44:y,45:E,46:v,47:L,48:M,49:b,50:D,51:c},e(k,[2,35]),e(k,[2,19]),{25:75,27:64,29:S},e(O,[2,22]),{26:[2,30]},{26:[2,20]}],defaultActions:{8:[2,1],10:[2,3],21:[2,2],74:[2,30],75:[2,20]},parseError:a(function(l,g){if(g.recoverable)this.trace(l);else{var d=new Error(l);throw d.hash=g,d}},"parseError"),parse:a(function(l){var g=this,d=[0],x=[],_=[null],r=[],lt=this.table,m="",dt=0,Ot=0,ui=2,Vt=1,di=r.slice.call(arguments,1),P=Object.create(this.lexer),$={yy:{}};for(var bt in this.yy)Object.prototype.hasOwnProperty.call(this.yy,bt)&&($.yy[bt]=this.yy[bt]);P.setInput(l,$.yy),$.yy.lexer=P,$.yy.parser=this,typeof P.yylloc>"u"&&(P.yylloc={});var wt=P.yylloc;r.push(wt);var xi=P.options&&P.options.ranges;typeof $.yy.parseError=="function"?this.parseError=$.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function fi(B){d.length=d.length-2*B,_.length=_.length-B,r.length=r.length-B}a(fi,"popStack");function Ft(){var B;return B=x.pop()||P.lex()||Vt,typeof B!="number"&&(B instanceof Array&&(x=B,B=x.pop()),B=g.symbols_[B]||B),B}a(Ft,"lex");for(var I,U,V,At,j={},xt,Y,Wt,ft;;){if(U=d[d.length-1],this.defaultActions[U]?V=this.defaultActions[U]:((I===null||typeof I>"u")&&(I=Ft()),V=lt[U]&<[U][I]),typeof V>"u"||!V.length||!V[0]){var Ct="";ft=[];for(xt in lt[U])this.terminals_[xt]&&xt>ui&&ft.push("'"+this.terminals_[xt]+"'");P.showPosition?Ct="Parse error on line "+(dt+1)+`:
|
|
2
2
|
`+P.showPosition()+`
|
|
3
3
|
Expecting `+ft.join(", ")+", got '"+(this.terminals_[I]||I)+"'":Ct="Parse error on line "+(dt+1)+": Unexpected "+(I==Vt?"end of input":"'"+(this.terminals_[I]||I)+"'"),this.parseError(Ct,{text:P.match,token:this.terminals_[I]||I,line:P.yylineno,loc:wt,expected:ft})}if(V[0]instanceof Array&&V.length>1)throw new Error("Parse Error: multiple actions possible at state: "+U+", token: "+I);switch(V[0]){case 1:d.push(I),_.push(P.yytext),r.push(P.yylloc),d.push(V[1]),I=null,Ot=P.yyleng,m=P.yytext,dt=P.yylineno,wt=P.yylloc;break;case 2:if(Y=this.productions_[V[1]][1],j.$=_[_.length-Y],j._$={first_line:r[r.length-(Y||1)].first_line,last_line:r[r.length-1].last_line,first_column:r[r.length-(Y||1)].first_column,last_column:r[r.length-1].last_column},xi&&(j._$.range=[r[r.length-(Y||1)].range[0],r[r.length-1].range[1]]),At=this.performAction.apply(j,[m,Ot,dt,$.yy,V[1],_,r].concat(di)),typeof At<"u")return At;Y&&(d=d.slice(0,-1*Y*2),_=_.slice(0,-1*Y),r=r.slice(0,-1*Y)),d.push(this.productions_[V[1]][0]),_.push(j.$),r.push(j._$),Wt=lt[d[d.length-2]][d[d.length-1]],d.push(Wt);break;case 3:return!0}}return!0},"parse")},ht=(function(){var W={EOF:1,parseError:a(function(g,d){if(this.yy.parser)this.yy.parser.parseError(g,d);else throw new Error(g)},"parseError"),setInput:a(function(l,g){return this.yy=g||this.yy||{},this._input=l,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:a(function(){var l=this._input[0];this.yytext+=l,this.yyleng++,this.offset++,this.match+=l,this.matched+=l;var g=l.match(/(?:\r\n?|\n).*/g);return g?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),l},"input"),unput:a(function(l){var g=l.length,d=l.split(/(?:\r\n?|\n)/g);this._input=l+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-g),this.offset-=g;var x=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),d.length-1&&(this.yylineno-=d.length-1);var _=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:d?(d.length===x.length?this.yylloc.first_column:0)+x[x.length-d.length].length-d[0].length:this.yylloc.first_column-g},this.options.ranges&&(this.yylloc.range=[_[0],_[0]+this.yyleng-g]),this.yyleng=this.yytext.length,this},"unput"),more:a(function(){return this._more=!0,this},"more"),reject:a(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
|
|
4
4
|
`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:a(function(l){this.unput(this.match.slice(l))},"less"),pastInput:a(function(){var l=this.matched.substr(0,this.matched.length-this.match.length);return(l.length>20?"...":"")+l.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:a(function(){var l=this.match;return l.length<20&&(l+=this._input.substr(0,20-l.length)),(l.substr(0,20)+(l.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:a(function(){var l=this.pastInput(),g=new Array(l.length+1).join("-");return l+this.upcomingInput()+`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON proxy of the navigation registry (`AdminLinkHandler`): the very search
|
|
3
|
+
* the assistant's `search-admin-links` skill performs, for the panel's global
|
|
4
|
+
* search palette and for hosts. Policies checked the auth; the registry drops
|
|
5
|
+
* every page this user may not open.
|
|
6
|
+
*/
|
|
7
|
+
export declare function searchAdminLinks(req: ReqType, res: ResType): Promise<ResType>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON proxy of the navigation registry (`AdminLinkHandler`): the very search
|
|
3
|
+
* the assistant's `search-admin-links` skill performs, for the panel's global
|
|
4
|
+
* search palette and for hosts. Policies checked the auth; the registry drops
|
|
5
|
+
* every page this user may not open.
|
|
6
|
+
*/
|
|
7
|
+
export async function searchAdminLinks(req, res) {
|
|
8
|
+
const query = typeof req.query.q === 'string' ? req.query.q : '';
|
|
9
|
+
return res.json({ results: await req.adminizer.adminLinkHandler.search(req.user, query) });
|
|
10
|
+
}
|
package/helpers/menuHelper.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @constructor
|
|
5
5
|
*/
|
|
6
6
|
import { User } from "../models/User.js";
|
|
7
|
-
import { ActionType, AdminpanelConfig, HrefConfig, ModelConfig, NavbarSectionConfig } from "../interfaces/adminpanelConfig.js";
|
|
7
|
+
import { ActionType, AdminpanelConfig, HrefConfig, MenuBadge, ModelConfig, NavbarSectionConfig } from "../interfaces/adminpanelConfig.js";
|
|
8
8
|
export type MenuItem = {
|
|
9
9
|
link: string;
|
|
10
10
|
title: string;
|
|
@@ -15,7 +15,17 @@ export type MenuItem = {
|
|
|
15
15
|
accessRightsToken: string | null;
|
|
16
16
|
modelResourceName?: string;
|
|
17
17
|
section?: string;
|
|
18
|
+
/** Raw value from the config; `listAccessibleMenuItems` resolves it per user. */
|
|
19
|
+
badge?: MenuBadge;
|
|
18
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* True when a model's item must not be in this user's navbar: `navbar.visible`
|
|
23
|
+
* is `false`, or `navbar.groupsAccessRights` names groups the user is not in.
|
|
24
|
+
* The navigation registry (`AdminLinkHandler`) applies the same rule to the
|
|
25
|
+
* model's record templates, so a model hidden from the menu is hidden from
|
|
26
|
+
* search, the assistant and breadcrumbs as well.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isModelHiddenInNavbar(user: User, config: ModelConfig): boolean;
|
|
19
29
|
export declare class MenuHelper {
|
|
20
30
|
private config;
|
|
21
31
|
constructor(config: AdminpanelConfig);
|
package/helpers/menuHelper.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { GroupsAccessRightsHelper } from "./accessRightsHelper.js";
|
|
2
|
+
/**
|
|
3
|
+
* True when a model's item must not be in this user's navbar: `navbar.visible`
|
|
4
|
+
* is `false`, or `navbar.groupsAccessRights` names groups the user is not in.
|
|
5
|
+
* The navigation registry (`AdminLinkHandler`) applies the same rule to the
|
|
6
|
+
* model's record templates, so a model hidden from the menu is hidden from
|
|
7
|
+
* search, the assistant and breadcrumbs as well.
|
|
8
|
+
*/
|
|
9
|
+
export function isModelHiddenInNavbar(user, config) {
|
|
10
|
+
if (typeof config.navbar?.visible === 'boolean')
|
|
11
|
+
return !config.navbar.visible;
|
|
12
|
+
if (config.navbar?.groupsAccessRights)
|
|
13
|
+
return !GroupsAccessRightsHelper.hasAccess(user, config.navbar.groupsAccessRights);
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
2
16
|
export class MenuHelper {
|
|
3
17
|
config;
|
|
4
18
|
constructor(config) {
|
|
@@ -179,17 +193,13 @@ export class MenuHelper {
|
|
|
179
193
|
icon: resolvedLink.icon || null,
|
|
180
194
|
accessRightsToken: resolvedLink.accessRightsToken || null,
|
|
181
195
|
section: resolvedLink.section || 'Platform',
|
|
196
|
+
badge: resolvedLink.badge,
|
|
182
197
|
});
|
|
183
198
|
});
|
|
184
199
|
if (this.config.models) {
|
|
185
200
|
const _this = this;
|
|
186
201
|
Object.entries(this.config.models).forEach(function ([key, val]) {
|
|
187
|
-
|
|
188
|
-
? !val.navbar.visible
|
|
189
|
-
: val.navbar?.groupsAccessRights
|
|
190
|
-
? !GroupsAccessRightsHelper.hasAccess(user, val.navbar.groupsAccessRights)
|
|
191
|
-
: false;
|
|
192
|
-
if (!hide) {
|
|
202
|
+
if (!isModelHiddenInNavbar(user, val)) {
|
|
193
203
|
if (val.tools && val.tools.length > 0 && val.tools[0].id !== "overview") {
|
|
194
204
|
val.tools.unshift({
|
|
195
205
|
id: "overview",
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import type { HrefConfig } from '../interfaces/adminpanelConfig.js';
|
|
2
|
-
import
|
|
1
|
+
import type { HrefConfig, MenuBadge } from '../interfaces/adminpanelConfig.js';
|
|
2
|
+
import { Adminizer } from '../lib/Adminizer.js';
|
|
3
3
|
import type { User } from '../models/User.js';
|
|
4
4
|
import type { MenuItem } from './menuHelper.js';
|
|
5
5
|
/** Drops the sub-items this user may not open, recursively. */
|
|
6
6
|
export declare function filterAccessibleHrefItems(adminizer: Adminizer, user: User, items: HrefConfig[]): Promise<HrefConfig[]>;
|
|
7
|
+
/**
|
|
8
|
+
* The badge of a menu item for this user: a plain value as is, a resolver
|
|
9
|
+
* called with the user. A failing resolver must not take the page down — it
|
|
10
|
+
* is logged and the item renders without a badge.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveMenuBadge(user: User, badge: MenuBadge | undefined, itemId: string): Promise<number | string | undefined>;
|
|
7
13
|
/**
|
|
8
14
|
* The navigation menu a user may actually open: configured models, additional
|
|
9
15
|
* links and app-contributed pages, filtered with the very same rules the
|
|
10
16
|
* sidebar uses. Titles are left untranslated, so callers that need i18n (the
|
|
11
|
-
* Inertia page props) translate on top of this.
|
|
17
|
+
* Inertia page props) translate on top of this. Badge resolvers are evaluated
|
|
18
|
+
* here, after the permission check, so they never run for a hidden page.
|
|
12
19
|
*/
|
|
13
20
|
export declare function listAccessibleMenuItems(adminizer: Adminizer, user: User): Promise<MenuItem[]>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Adminizer } from '../lib/Adminizer.js';
|
|
1
2
|
/** Drops the sub-items this user may not open, recursively. */
|
|
2
3
|
export async function filterAccessibleHrefItems(adminizer, user, items) {
|
|
3
4
|
const accessible = [];
|
|
@@ -12,11 +13,28 @@ export async function filterAccessibleHrefItems(adminizer, user, items) {
|
|
|
12
13
|
}
|
|
13
14
|
return accessible;
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* The badge of a menu item for this user: a plain value as is, a resolver
|
|
18
|
+
* called with the user. A failing resolver must not take the page down — it
|
|
19
|
+
* is logged and the item renders without a badge.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveMenuBadge(user, badge, itemId) {
|
|
22
|
+
if (typeof badge !== 'function')
|
|
23
|
+
return badge;
|
|
24
|
+
try {
|
|
25
|
+
return await badge(user);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
Adminizer.log.error(`navigation > badge of "${itemId}" failed: ${error}`);
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
15
32
|
/**
|
|
16
33
|
* The navigation menu a user may actually open: configured models, additional
|
|
17
34
|
* links and app-contributed pages, filtered with the very same rules the
|
|
18
35
|
* sidebar uses. Titles are left untranslated, so callers that need i18n (the
|
|
19
|
-
* Inertia page props) translate on top of this.
|
|
36
|
+
* Inertia page props) translate on top of this. Badge resolvers are evaluated
|
|
37
|
+
* here, after the permission check, so they never run for a hidden page.
|
|
20
38
|
*/
|
|
21
39
|
export async function listAccessibleMenuItems(adminizer, user) {
|
|
22
40
|
const menu = [];
|
|
@@ -26,7 +44,7 @@ export async function listAccessibleMenuItems(adminizer, user) {
|
|
|
26
44
|
if (menuItem.accessRightsToken)
|
|
27
45
|
tokens.push(menuItem.accessRightsToken);
|
|
28
46
|
if (await adminizer.accessRightsHelper.checkAnyPermission(tokens, user)) {
|
|
29
|
-
menu.push({ ...menuItem, actions });
|
|
47
|
+
menu.push({ ...menuItem, actions, badge: await resolveMenuBadge(user, menuItem.badge, menuItem.id) });
|
|
30
48
|
}
|
|
31
49
|
}
|
|
32
50
|
for (const link of await adminizer.adminLinkHandler.list(user)) {
|
|
@@ -39,6 +57,7 @@ export async function listAccessibleMenuItems(adminizer, user) {
|
|
|
39
57
|
icon: null,
|
|
40
58
|
accessRightsToken: link.accessRightsToken ?? null,
|
|
41
59
|
section: link.section || 'Platform',
|
|
60
|
+
badge: await resolveMenuBadge(user, link.badge, link.id),
|
|
42
61
|
});
|
|
43
62
|
}
|
|
44
63
|
return menu;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matches an Express-style pattern (`/model/:name`) against a concrete path.
|
|
3
|
+
* Query, hash and trailing slashes are ignored on both sides.
|
|
4
|
+
*/
|
|
5
|
+
export function matchesPathPattern(pattern, url) {
|
|
6
|
+
const path = url.replace(/[?#].*$/, '').replace(/\/+$/, '') || '/';
|
|
7
|
+
const source = pattern.replace(/\/+$/, '') || '/';
|
|
8
|
+
const regex = new RegExp(`^${source.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/:[A-Za-z0-9_]+/g, '[^/]+')}$`);
|
|
9
|
+
return regex.test(path);
|
|
10
|
+
}
|
|
@@ -135,6 +135,13 @@ export interface AdminpanelConfig {
|
|
|
135
135
|
sections?: {
|
|
136
136
|
[section: string]: NavbarSectionConfig;
|
|
137
137
|
};
|
|
138
|
+
/**
|
|
139
|
+
* Breadcrumbs of pages that send none of their own (`breadcrumbs` prop).
|
|
140
|
+
* `'auto'` resolves them from the navigation registry: menu items, app
|
|
141
|
+
* links and link templates whose URL is a prefix of the current one.
|
|
142
|
+
* `'manual'` (default) shows only what a page sends.
|
|
143
|
+
*/
|
|
144
|
+
breadcrumbs?: 'manual' | 'auto';
|
|
138
145
|
};
|
|
139
146
|
/**
|
|
140
147
|
* Policies that will be executed before going to every page
|
|
@@ -703,6 +710,14 @@ export interface CreateUpdateConfig {
|
|
|
703
710
|
* */
|
|
704
711
|
controller?: string | ControllerFunction;
|
|
705
712
|
}
|
|
713
|
+
/**
|
|
714
|
+
* Badge of a sidebar item: a value, or a resolver computing it for the user
|
|
715
|
+
* the menu is built for. A resolver runs on every request and only for items
|
|
716
|
+
* that user may open, so it must be cheap — return a cached count, never run a
|
|
717
|
+
* query per call. A resolver that throws is logged and renders no badge.
|
|
718
|
+
* `0`, `''` and `undefined` render nothing.
|
|
719
|
+
*/
|
|
720
|
+
export type MenuBadge = number | string | ((user: User) => number | string | undefined | Promise<number | string | undefined>);
|
|
706
721
|
export interface HrefConfig {
|
|
707
722
|
id: string;
|
|
708
723
|
title: string;
|
|
@@ -724,6 +739,14 @@ export interface HrefConfig {
|
|
|
724
739
|
* Optional section grouping for navbar items (side navigation)
|
|
725
740
|
*/
|
|
726
741
|
section?: string;
|
|
742
|
+
/**
|
|
743
|
+
* Count shown next to the item in the expanded sidebar (rendered as
|
|
744
|
+
* `SidebarMenuBadge`). `0`, `''` and `undefined` render nothing. Not drawn
|
|
745
|
+
* in the collapsed icon rail, where a number without a label means nothing.
|
|
746
|
+
* Either a value or a resolver computing it per request for the user the
|
|
747
|
+
* menu is built for — see `MenuBadge`.
|
|
748
|
+
*/
|
|
749
|
+
badge?: MenuBadge;
|
|
727
750
|
}
|
|
728
751
|
/**
|
|
729
752
|
* Presentation of a single navbar section header.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type MenuItem } from '../../helpers/menuHelper.js';
|
|
2
|
+
import type { MenuBadge } from '../../interfaces/adminpanelConfig.js';
|
|
1
3
|
import type { User } from '../../models/User.js';
|
|
2
4
|
import type { Adminizer } from '../Adminizer.js';
|
|
3
5
|
export interface AdminLink {
|
|
@@ -8,6 +10,8 @@ export interface AdminLink {
|
|
|
8
10
|
title?: string;
|
|
9
11
|
section?: string;
|
|
10
12
|
accessRightsToken?: string;
|
|
13
|
+
/** Count shown next to the item in the sidebar: a value or a per-user resolver, see `MenuBadge`. */
|
|
14
|
+
badge?: MenuBadge;
|
|
11
15
|
}
|
|
12
16
|
export interface ResolvedAdminLink extends AdminLink {
|
|
13
17
|
owner: string;
|
|
@@ -38,6 +42,24 @@ export interface ResolvedAdminLinkTemplate extends AdminLinkTemplate {
|
|
|
38
42
|
owner: string;
|
|
39
43
|
params: AdminLinkTemplateParam[];
|
|
40
44
|
}
|
|
45
|
+
/** A hit of `AdminLinkHandler.search`: a concrete page or a template plus its placeholders. */
|
|
46
|
+
export interface AdminLinkSearchResult {
|
|
47
|
+
id: string;
|
|
48
|
+
title: string;
|
|
49
|
+
/** Concrete page, ready to open. Absent for templates. */
|
|
50
|
+
link?: string;
|
|
51
|
+
/** Parametrized page: fill `params` and resolve through `resolveTemplate`. */
|
|
52
|
+
template?: string;
|
|
53
|
+
/** Placeholders that must be filled to open a template. */
|
|
54
|
+
params?: AdminLinkTemplateParam[];
|
|
55
|
+
description?: string;
|
|
56
|
+
section?: string;
|
|
57
|
+
}
|
|
58
|
+
/** One crumb of `AdminLinkHandler.locate`; the page itself carries no `href`. */
|
|
59
|
+
export interface AdminBreadcrumb {
|
|
60
|
+
title: string;
|
|
61
|
+
href?: string;
|
|
62
|
+
}
|
|
41
63
|
/** Server-side registry of standalone admin pages contributed by applications. */
|
|
42
64
|
export declare class AdminLinkHandler {
|
|
43
65
|
private readonly adminizer;
|
|
@@ -61,6 +83,25 @@ export declare class AdminLinkHandler {
|
|
|
61
83
|
* admin URL, rejecting templates this user may not open.
|
|
62
84
|
*/
|
|
63
85
|
resolveTemplate(user: User, id: string, params?: Record<string, unknown>): Promise<string>;
|
|
86
|
+
/**
|
|
87
|
+
* Search exactly the navigation a user can open: the menu the sidebar
|
|
88
|
+
* renders (sub-items included) plus every link template. Permission-safe by
|
|
89
|
+
* construction, so the result may be handed to an agent or to the browser.
|
|
90
|
+
* Parametrized pages are returned as templates, so a record page is
|
|
91
|
+
* reachable even though its URL only exists once the id is known.
|
|
92
|
+
*/
|
|
93
|
+
search(user: User, query?: string): Promise<AdminLinkSearchResult[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Breadcrumb chain of an admin url: every page of the registry — menu items
|
|
96
|
+
* and their actions, app links, link templates — whose path is a prefix of
|
|
97
|
+
* it, shortest first. The last crumb has no `href` when it is the page
|
|
98
|
+
* itself. Pages this user may not open never appear.
|
|
99
|
+
*
|
|
100
|
+
* `menu` lets a caller that already holds the user's menu (bindInertia
|
|
101
|
+
* computes a translated one per request) skip a second computation.
|
|
102
|
+
*/
|
|
103
|
+
locate(user: User, url: string, menu?: MenuItem[]): Promise<AdminBreadcrumb[]>;
|
|
104
|
+
private normalizePath;
|
|
64
105
|
/** Fills the placeholders of a template with url-encoded values. */
|
|
65
106
|
buildHref(template: ResolvedAdminLinkTemplate, params?: Record<string, unknown>): string;
|
|
66
107
|
/** True for paths that change data and therefore must never be navigated to. */
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { listAccessibleMenuItems } from '../../helpers/navigationAccessHelper.js';
|
|
2
|
+
import { matchesPathPattern } from '../../helpers/pathPattern.js';
|
|
3
|
+
import { isModelHiddenInNavbar } from '../../helpers/menuHelper.js';
|
|
1
4
|
/** Segments no template may point at: opening one mutates data. */
|
|
2
5
|
const DESTRUCTIVE_PATH = /\/model\/[^/]+\/remove(\/|$)/i;
|
|
3
6
|
/** Server-side registry of standalone admin pages contributed by applications. */
|
|
@@ -108,6 +111,92 @@ export class AdminLinkHandler {
|
|
|
108
111
|
throw new Error(`Admin link template "${id}" is not available`);
|
|
109
112
|
return this.buildHref(template, params);
|
|
110
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Search exactly the navigation a user can open: the menu the sidebar
|
|
116
|
+
* renders (sub-items included) plus every link template. Permission-safe by
|
|
117
|
+
* construction, so the result may be handed to an agent or to the browser.
|
|
118
|
+
* Parametrized pages are returned as templates, so a record page is
|
|
119
|
+
* reachable even though its URL only exists once the id is known.
|
|
120
|
+
*/
|
|
121
|
+
async search(user, query = '') {
|
|
122
|
+
const needle = this.slug(query);
|
|
123
|
+
const result = [];
|
|
124
|
+
const matches = (...values) => !needle || this.slug(values.filter(Boolean).join(' ')).includes(needle);
|
|
125
|
+
const add = (item, section) => {
|
|
126
|
+
if (!item?.link || !item?.title)
|
|
127
|
+
return;
|
|
128
|
+
const link = {
|
|
129
|
+
id: String(item.id || item.title), title: String(item.title), link: String(item.link),
|
|
130
|
+
section: item.section || section,
|
|
131
|
+
};
|
|
132
|
+
// A link with placeholders is surfaced as a template instead.
|
|
133
|
+
if (!/:[A-Za-z0-9_]+/.test(link.link) && matches(link.title, link.id, link.section))
|
|
134
|
+
result.push(link);
|
|
135
|
+
for (const child of item.actions ?? item.subItems ?? [])
|
|
136
|
+
add(child, link.section);
|
|
137
|
+
};
|
|
138
|
+
for (const item of await listAccessibleMenuItems(this.adminizer, user))
|
|
139
|
+
add(item, item.section);
|
|
140
|
+
for (const template of await this.listTemplates(user)) {
|
|
141
|
+
if (!matches(template.title, template.id, template.section, template.description, template.template))
|
|
142
|
+
continue;
|
|
143
|
+
result.push({
|
|
144
|
+
id: template.id,
|
|
145
|
+
title: template.title,
|
|
146
|
+
template: template.template,
|
|
147
|
+
params: template.params,
|
|
148
|
+
description: template.description,
|
|
149
|
+
section: template.section,
|
|
150
|
+
// A template without placeholders is already a usable link.
|
|
151
|
+
link: template.params.length ? undefined : template.template,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Breadcrumb chain of an admin url: every page of the registry — menu items
|
|
158
|
+
* and their actions, app links, link templates — whose path is a prefix of
|
|
159
|
+
* it, shortest first. The last crumb has no `href` when it is the page
|
|
160
|
+
* itself. Pages this user may not open never appear.
|
|
161
|
+
*
|
|
162
|
+
* `menu` lets a caller that already holds the user's menu (bindInertia
|
|
163
|
+
* computes a translated one per request) skip a second computation.
|
|
164
|
+
*/
|
|
165
|
+
async locate(user, url, menu) {
|
|
166
|
+
const prefix = (this.adminizer.config.routePrefix || '').replace(/\/+$/, '');
|
|
167
|
+
const path = this.normalizePath(url);
|
|
168
|
+
if (path !== prefix && !path.startsWith(`${prefix}/`))
|
|
169
|
+
return [];
|
|
170
|
+
// Concrete pages: exact path -> title.
|
|
171
|
+
const pages = new Map();
|
|
172
|
+
const collect = (item) => {
|
|
173
|
+
const link = typeof item?.link === 'string' ? item.link : '';
|
|
174
|
+
if (link.startsWith('/') && item?.title && !/:[A-Za-z0-9_]+/.test(link)) {
|
|
175
|
+
pages.set(this.normalizePath(link), String(item.title));
|
|
176
|
+
}
|
|
177
|
+
for (const child of item?.actions ?? item?.subItems ?? [])
|
|
178
|
+
collect(child);
|
|
179
|
+
};
|
|
180
|
+
for (const item of menu ?? await listAccessibleMenuItems(this.adminizer, user))
|
|
181
|
+
collect(item);
|
|
182
|
+
const templates = await this.listTemplates(user);
|
|
183
|
+
const crumbs = [];
|
|
184
|
+
const segments = path.slice(prefix.length).split('/').filter(Boolean);
|
|
185
|
+
for (let depth = 1; depth <= segments.length; depth++) {
|
|
186
|
+
const candidate = `${prefix}/${segments.slice(0, depth).join('/')}`;
|
|
187
|
+
const title = pages.get(candidate)
|
|
188
|
+
?? templates.find((template) => matchesPathPattern(template.template, candidate))?.title;
|
|
189
|
+
if (title)
|
|
190
|
+
crumbs.push({ title, href: candidate });
|
|
191
|
+
}
|
|
192
|
+
const last = crumbs[crumbs.length - 1];
|
|
193
|
+
if (last?.href === path)
|
|
194
|
+
delete last.href;
|
|
195
|
+
return crumbs;
|
|
196
|
+
}
|
|
197
|
+
normalizePath(url) {
|
|
198
|
+
return url.replace(/[?#].*$/, '').replace(/\/+$/, '') || '/';
|
|
199
|
+
}
|
|
111
200
|
/** Fills the placeholders of a template with url-encoded values. */
|
|
112
201
|
buildHref(template, params = {}) {
|
|
113
202
|
return template.template.replace(/:([A-Za-z0-9_]+)/g, (_match, name) => {
|
|
@@ -136,6 +225,10 @@ export class AdminLinkHandler {
|
|
|
136
225
|
{ name: 'id', description: 'Identifier of the record, as returned when reading its data.' },
|
|
137
226
|
];
|
|
138
227
|
for (const [model, config] of Object.entries(this.adminizer.config.models ?? {})) {
|
|
228
|
+
// A model kept out of this user's navbar (`navbar.visible: false`,
|
|
229
|
+
// `navbar.groupsAccessRights`) is kept out of the registry as well.
|
|
230
|
+
if (typeof config === 'object' && config && isModelHiddenInNavbar(user, config))
|
|
231
|
+
continue;
|
|
139
232
|
const title = typeof config === 'object' && config?.title ? config.title : model;
|
|
140
233
|
const section = typeof config === 'object' ? config?.navbar?.section : undefined;
|
|
141
234
|
builtin(`model-${model}-edit`, `${title}: open record`, `${prefix}/model/${model}/edit/:id`, `update-${model}-model`, `Open the edit page of a single ${title} record by its id.`, section, recordId);
|
|
@@ -178,6 +271,6 @@ export class AdminLinkHandler {
|
|
|
178
271
|
}));
|
|
179
272
|
}
|
|
180
273
|
slug(value) {
|
|
181
|
-
return String(value).trim().toLowerCase().replace(/[\s_-]+/g, '-');
|
|
274
|
+
return String(value ?? '').trim().toLowerCase().replace(/[\s_-]+/g, '-');
|
|
182
275
|
}
|
|
183
276
|
}
|
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import type { User } from '../../models/User.js';
|
|
2
2
|
import type { Adminizer } from '../Adminizer.js';
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
title: string;
|
|
7
|
-
/** Concrete page, ready for `navigate`. Absent for templates. */
|
|
8
|
-
link?: string;
|
|
9
|
-
/** Parametrized page: pass `template` plus `params` to `navigate`. */
|
|
10
|
-
template?: string;
|
|
11
|
-
/** Placeholders that must be filled to open a template. */
|
|
12
|
-
params?: AdminLinkTemplateParam[];
|
|
13
|
-
description?: string;
|
|
14
|
-
section?: string;
|
|
15
|
-
}
|
|
3
|
+
import type { AdminLinkSearchResult } from '../admin-links/AdminLinkHandler.js';
|
|
4
|
+
/** @deprecated Use `AdminLinkSearchResult`; kept as an alias for compatibility. */
|
|
5
|
+
export type AiAssistantAdminLink = AdminLinkSearchResult;
|
|
16
6
|
/**
|
|
17
7
|
* A browser capability an AI service may expose as a tool. The agent emits
|
|
18
8
|
* `ui.method` with this id; the assistant panel performs the browser action.
|
|
@@ -41,14 +31,7 @@ export declare class AiAssistantUiMethodHandler {
|
|
|
41
31
|
register(method: AiAssistantUiMethod, owner?: string): void;
|
|
42
32
|
unregister(id: string, owner?: string): void;
|
|
43
33
|
getAvailable(user: User): Promise<AiAssistantUiMethod[]>;
|
|
44
|
-
/**
|
|
45
|
-
* Search exactly the navigation a user can open. This intentionally uses
|
|
46
|
-
* MenuHelper rather than client-side markup, therefore a tool result is
|
|
47
|
-
* permission-safe and works when the assistant panel is not visible.
|
|
48
|
-
*
|
|
49
|
-
* Parametrized pages are returned as templates, so a record page is
|
|
50
|
-
* reachable even though its URL only exists once the id is known.
|
|
51
|
-
*/
|
|
34
|
+
/** The registry search (`AdminLinkHandler.search`), kept here for model services. */
|
|
52
35
|
searchAdminLinks(user: User, query?: string): Promise<AiAssistantAdminLink[]>;
|
|
53
36
|
/**
|
|
54
37
|
* Validates what an agent asked to open and returns the concrete admin URL.
|
|
@@ -56,5 +39,4 @@ export declare class AiAssistantUiMethodHandler {
|
|
|
56
39
|
* can only reach pages that user could reach by clicking.
|
|
57
40
|
*/
|
|
58
41
|
resolveNavigation(user: User, target: AiAssistantNavigationTarget): Promise<string>;
|
|
59
|
-
private slug;
|
|
60
42
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { listAccessibleMenuItems } from '../../helpers/navigationAccessHelper.js';
|
|
2
1
|
export class AiAssistantUiMethodHandler {
|
|
3
2
|
adminizer;
|
|
4
3
|
methods = new Map();
|
|
@@ -69,49 +68,9 @@ export class AiAssistantUiMethodHandler {
|
|
|
69
68
|
}
|
|
70
69
|
return available;
|
|
71
70
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* permission-safe and works when the assistant panel is not visible.
|
|
76
|
-
*
|
|
77
|
-
* Parametrized pages are returned as templates, so a record page is
|
|
78
|
-
* reachable even though its URL only exists once the id is known.
|
|
79
|
-
*/
|
|
80
|
-
async searchAdminLinks(user, query = '') {
|
|
81
|
-
const needle = this.slug(query);
|
|
82
|
-
const result = [];
|
|
83
|
-
const matches = (...values) => !needle || this.slug(values.filter(Boolean).join(' ')).includes(needle);
|
|
84
|
-
const add = (item, section) => {
|
|
85
|
-
if (!item?.link || !item?.title)
|
|
86
|
-
return;
|
|
87
|
-
const link = {
|
|
88
|
-
id: String(item.id || item.title), title: String(item.title), link: String(item.link),
|
|
89
|
-
section: item.section || section,
|
|
90
|
-
};
|
|
91
|
-
// A link with placeholders is surfaced as a template instead.
|
|
92
|
-
if (!/:[A-Za-z0-9_]+/.test(link.link) && matches(link.title, link.id, link.section))
|
|
93
|
-
result.push(link);
|
|
94
|
-
for (const child of item.actions ?? item.subItems ?? [])
|
|
95
|
-
add(child, link.section);
|
|
96
|
-
};
|
|
97
|
-
// Exactly the menu the sidebar renders for this user, sub-items included.
|
|
98
|
-
for (const item of await listAccessibleMenuItems(this.adminizer, user))
|
|
99
|
-
add(item, item.section);
|
|
100
|
-
for (const template of await this.adminizer.adminLinkHandler.listTemplates(user)) {
|
|
101
|
-
if (!matches(template.title, template.id, template.section, template.description, template.template))
|
|
102
|
-
continue;
|
|
103
|
-
result.push({
|
|
104
|
-
id: template.id,
|
|
105
|
-
title: template.title,
|
|
106
|
-
template: template.template,
|
|
107
|
-
params: template.params,
|
|
108
|
-
description: template.description,
|
|
109
|
-
section: template.section,
|
|
110
|
-
// A template without placeholders is already a usable link.
|
|
111
|
-
link: template.params.length ? undefined : template.template,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
return result;
|
|
71
|
+
/** The registry search (`AdminLinkHandler.search`), kept here for model services. */
|
|
72
|
+
searchAdminLinks(user, query = '') {
|
|
73
|
+
return this.adminizer.adminLinkHandler.search(user, query);
|
|
115
74
|
}
|
|
116
75
|
/**
|
|
117
76
|
* Validates what an agent asked to open and returns the concrete admin URL.
|
|
@@ -138,7 +97,4 @@ export class AiAssistantUiMethodHandler {
|
|
|
138
97
|
}
|
|
139
98
|
return href;
|
|
140
99
|
}
|
|
141
|
-
slug(value) {
|
|
142
|
-
return String(value ?? '').trim().toLowerCase().replace(/[\s_-]+/g, '-');
|
|
143
|
-
}
|
|
144
100
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { matchesPathPattern } from '../../helpers/pathPattern.js';
|
|
1
2
|
const SNIPPET_LIMIT = 3;
|
|
2
3
|
const SNIPPET_CONTEXT = 120;
|
|
3
4
|
/**
|
|
@@ -85,10 +86,7 @@ export class AbstractDocumentation {
|
|
|
85
86
|
}
|
|
86
87
|
/** Matches an Express-style pattern (`/model/:name`) against a concrete path. */
|
|
87
88
|
matchesUrl(pattern, url) {
|
|
88
|
-
|
|
89
|
-
const source = pattern.replace(/\/+$/, '') || '/';
|
|
90
|
-
const regex = new RegExp(`^${source.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/:[A-Za-z0-9_]+/g, '[^/]+')}$`);
|
|
91
|
-
return regex.test(path);
|
|
89
|
+
return matchesPathPattern(pattern, url);
|
|
92
90
|
}
|
|
93
91
|
/** Lines containing the query, trimmed to a little context around the match. */
|
|
94
92
|
extractSnippets(markdown, query) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adminizer",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.1.0-build.
|
|
4
|
+
"version": "5.1.0-build.28",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|
|
8
8
|
"./lib/DataAccessor": "./dist/lib/DataAccessor.js",
|
|
9
|
-
"./ui/*": "./dist/ui/components/ui/*.js",
|
|
10
9
|
"./adminizer-module": "./adminizer-module.d.ts"
|
|
11
10
|
},
|
|
12
11
|
"scripts": {
|
package/system/Router.js
CHANGED
|
@@ -33,6 +33,7 @@ import { HistoryController } from "../controllers/history-actions/HistoryControl
|
|
|
33
33
|
import { AiAssistantController } from "../controllers/ai/AiAssistantController.js";
|
|
34
34
|
import { AiAgentController } from "../controllers/ai/AiAgentController.js";
|
|
35
35
|
import listUserFilters from "../controllers/listUserFilters.js";
|
|
36
|
+
import { searchAdminLinks } from "../controllers/adminLinks.js";
|
|
36
37
|
import { requireAnyPermission, requireAuthAPI, requireAuthEnabled, requireAuthUI, requirePermission } from "../policies/authPolicies.js";
|
|
37
38
|
import { aiModelToken, catalogToken, groupFilterVisibilityToken, historyToken, mediaManagerToken, modelCreateToken, modelDeleteToken, modelReadToken, modelUpdateToken, widgetToken, widgetsToken } from "../policies/permissionResolvers.js";
|
|
38
39
|
import { bindWithMiddlewares } from "./routePolicies.js";
|
|
@@ -222,6 +223,12 @@ export default class Router {
|
|
|
222
223
|
*/
|
|
223
224
|
adminizer.app.get(`${adminizer.config.routePrefix}/api/user-key`, withPolicies(getUserApiKey, requireAuthEnabled(), requireAuthAPI()));
|
|
224
225
|
adminizer.app.post(`${adminizer.config.routePrefix}/api/user-key/regenerate`, withPolicies(regenerateUserApiKey, requireAuthEnabled(), requireAuthAPI()));
|
|
226
|
+
/**
|
|
227
|
+
* Navigation registry search — consumed by the global search palette
|
|
228
|
+
* (src/assets/js/components/global-search.tsx). Results are filtered by
|
|
229
|
+
* the user's permissions inside the registry.
|
|
230
|
+
*/
|
|
231
|
+
adminizer.app.get(`${adminizer.config.routePrefix}/api/links/search`, withPolicies(searchAdminLinks, requireAuthAPI()));
|
|
225
232
|
/**
|
|
226
233
|
* AI assistant — consumed by src/assets/js/contexts/AiAssistantContext.tsx
|
|
227
234
|
*/
|