@hienlh/ppm 0.13.76 → 0.13.78

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.
Files changed (130) hide show
  1. package/.opencode/.env.example +98 -0
  2. package/.opencode/skills/ads-management/scripts/.env.example +13 -0
  3. package/.opencode/skills/ai-multimodal/.env.example +230 -0
  4. package/.opencode/skills/cip-design/.env.example +6 -0
  5. package/.opencode/skills/devops/.env.example +76 -0
  6. package/.opencode/skills/docs-seeker/.env.example +15 -0
  7. package/.opencode/skills/elevenlabs/.env.example +3 -0
  8. package/.opencode/skills/marketing-dashboard/.env.example +15 -0
  9. package/.opencode/skills/marketing-dashboard/app/.env.example +2 -0
  10. package/.opencode/skills/marketing-dashboard/server/.env.example +2 -0
  11. package/.opencode/skills/mcp-management/scripts/dist/analyze-tools.js +70 -0
  12. package/.opencode/skills/mcp-management/scripts/dist/cli.js +160 -0
  13. package/.opencode/skills/mcp-management/scripts/dist/mcp-client.js +183 -0
  14. package/.opencode/skills/payment-integration/scripts/.env.example +20 -0
  15. package/.opencode/skills/sequential-thinking/.env.example +8 -0
  16. package/CHANGELOG.md +10 -0
  17. package/assets/skills/ppm/SKILL.md +1 -1
  18. package/assets/skills/ppm/references/cli-reference.md +4 -4
  19. package/assets/skills/ppm/references/http-api.md +1 -7
  20. package/dist/web/assets/{api-settings-Byph7lae.js → api-settings-BJTjIG4U.js} +1 -1
  21. package/dist/web/assets/architecture-PBZL5I3N-CkdUQjA_.js +1 -0
  22. package/dist/web/assets/{audio-preview-DXCxll7f.js → audio-preview-DFrv27cv.js} +1 -1
  23. package/dist/web/assets/chat-tab-DNmhvaZ_.js +16 -0
  24. package/dist/web/assets/code-editor-CHwXYDhx.js +10 -0
  25. package/dist/web/assets/{conflict-editor-RMsNwUKp.js → conflict-editor-DGm0Z6Mc.js} +1 -1
  26. package/dist/web/assets/{csv-preview-CwEbP_iZ.js → csv-preview-asMfgR0r.js} +1 -1
  27. package/dist/web/assets/{data-grid-overlay-editor-C36FRqE8.js → data-grid-overlay-editor-DGjqvYn6.js} +1 -1
  28. package/dist/web/assets/database-viewer-D1nO6a11.js +1 -0
  29. package/dist/web/assets/{diff-viewer--SSMwMoS.js → diff-viewer-Blji3pAc.js} +1 -1
  30. package/dist/web/assets/{docx-preview-BL398ELS.js → docx-preview-BRpopR6E.js} +1 -1
  31. package/dist/web/assets/{esm-DH3rpl0I.js → esm-xVTUq__o.js} +1 -1
  32. package/dist/web/assets/{extension-webview-DrnDwQpM.js → extension-webview-BXeDxyVt.js} +2 -2
  33. package/dist/web/assets/git-log-panel-B5n35kot.js +1 -0
  34. package/dist/web/assets/gitGraph-HDMCJU4V-D3UR56AG.js +1 -0
  35. package/dist/web/assets/glide-data-grid-4mwS1Swl.js +138 -0
  36. package/dist/web/assets/{image-preview-DeNH71Ez.js → image-preview-Bu5XcdqW.js} +1 -1
  37. package/dist/web/assets/index-CuOntRL_.js +27 -0
  38. package/dist/web/assets/info-3K5VOQVL-DUhLSKI2.js +1 -0
  39. package/dist/web/assets/{input-DSELw5zU.js → input-CArJe9WS.js} +1 -1
  40. package/dist/web/assets/keybindings-store-Clt4mfff.js +1 -0
  41. package/dist/web/assets/{markdown-renderer-D5B629qw.js → markdown-renderer-Be-gcsCi.js} +3 -3
  42. package/dist/web/assets/notification-store-B0D4A68q.js +1 -0
  43. package/dist/web/assets/{number-overlay-editor-JsUdft7z.js → number-overlay-editor-DtUBprPW.js} +1 -1
  44. package/dist/web/assets/packet-RMMSAZCW-BIpeVUGW.js +1 -0
  45. package/dist/web/assets/{panel-store-DlvwzOll.js → panel-store-C9VAhbZz.js} +1 -1
  46. package/dist/web/assets/{pdf-preview-yUMARG8r.js → pdf-preview-hiA6Dkic.js} +1 -1
  47. package/dist/web/assets/pie-UPGHQEXC-CNoizzjb.js +1 -0
  48. package/dist/web/assets/port-forwarding-tab-DTPLT4nk.js +1 -0
  49. package/dist/web/assets/{postgres-viewer-C28tRuh5.js → postgres-viewer-LimIiFby.js} +3 -3
  50. package/dist/web/assets/{project-store-CpC02pIv.js → project-store-DlbHpIq0.js} +1 -1
  51. package/dist/web/assets/radar-KQ55EAFF-7dns-ho5.js +1 -0
  52. package/dist/web/assets/{settings-store-MXJgFUnl.js → settings-store-DQUFTPk2.js} +2 -2
  53. package/dist/web/assets/settings-tab-B1wiEtFH.js +1 -0
  54. package/dist/web/assets/sql-query-editor-DGcroNAs.js +1 -0
  55. package/dist/web/assets/sqlite-viewer-DIfCI2va.js +1 -0
  56. package/dist/web/assets/system-monitor-tab-CIVm3sgY.js +1 -0
  57. package/dist/web/assets/{tab-store-Bdw8DIbZ.js → tab-store-CIcbSn0c.js} +1 -1
  58. package/dist/web/assets/{terminal-tab-B1_sAuIi.js → terminal-tab-Dj9xDdbc.js} +2 -2
  59. package/dist/web/assets/treemap-KZPCXAKY-D3DZCLoE.js +1 -0
  60. package/dist/web/assets/{use-blob-url-CBi0HMq5.js → use-blob-url-DrPfBQBM.js} +1 -1
  61. package/dist/web/assets/{use-monaco-theme-CbzQcrwD.js → use-monaco-theme-BLIgarH5.js} +1 -1
  62. package/dist/web/assets/{vendor-mermaid-D2cOxeao.js → vendor-mermaid-DkqjpqJK.js} +3 -3
  63. package/dist/web/assets/{video-preview-D-nAiQsv.js → video-preview-CnRJmbG8.js} +1 -1
  64. package/dist/web/index.html +17 -20
  65. package/dist/web/sw.js +1 -1
  66. package/package.json +1 -1
  67. package/src/index.ts +0 -0
  68. package/src/server/index.ts +0 -2
  69. package/src/services/cloud-ws.service.ts +27 -1
  70. package/src/services/config.service.ts +1 -1
  71. package/src/services/db.service.ts +0 -24
  72. package/src/services/notification.service.ts +14 -5
  73. package/src/types/config.ts +0 -7
  74. package/src/web/components/database/sql-completion-provider.ts +28 -1
  75. package/src/web/components/database/sql-query-editor.tsx +1 -30
  76. package/src/web/components/editor/code-editor.tsx +14 -1
  77. package/src/web/components/settings/settings-tab.tsx +12 -66
  78. package/src/web/sw.ts +0 -45
  79. package/bun.lock +0 -2142
  80. package/bunfig.toml +0 -2
  81. package/dist/web/assets/ai-settings-section-D0VMZ4aE.js +0 -1
  82. package/dist/web/assets/architecture-PBZL5I3N-DyeqgxGw.js +0 -1
  83. package/dist/web/assets/chat-tab-D5j5gP5j.js +0 -16
  84. package/dist/web/assets/code-editor-CL2gcvDj.js +0 -10
  85. package/dist/web/assets/database-viewer-2TbU0m7s.js +0 -1
  86. package/dist/web/assets/git-log-panel-u6ehykcl.js +0 -1
  87. package/dist/web/assets/gitGraph-HDMCJU4V-CZAWeLUi.js +0 -1
  88. package/dist/web/assets/glide-data-grid-Benw7NI4.js +0 -136
  89. package/dist/web/assets/globe-CQ8NAYvi.js +0 -1
  90. package/dist/web/assets/index-BS3CQIT3.js +0 -27
  91. package/dist/web/assets/info-3K5VOQVL-BqVOLnRc.js +0 -1
  92. package/dist/web/assets/keybindings-store-gFa7vUoe.js +0 -1
  93. package/dist/web/assets/notification-store-qViiZZaY.js +0 -1
  94. package/dist/web/assets/packet-RMMSAZCW-BGMrAgbD.js +0 -1
  95. package/dist/web/assets/pie-UPGHQEXC-9IRPAyAe.js +0 -1
  96. package/dist/web/assets/port-forwarding-tab-D7rVcasa.js +0 -1
  97. package/dist/web/assets/radar-KQ55EAFF-CxjdSb6M.js +0 -1
  98. package/dist/web/assets/refresh-cw-CRD2qr4U.js +0 -1
  99. package/dist/web/assets/settings-tab-0kWZtlCi.js +0 -1
  100. package/dist/web/assets/sql-query-editor-DCx7kPlY.js +0 -3
  101. package/dist/web/assets/sqlite-viewer-W1RhaCr0.js +0 -1
  102. package/dist/web/assets/system-monitor-tab-CJojQd3x.js +0 -1
  103. package/dist/web/assets/treemap-KZPCXAKY-BYrmfSj5.js +0 -1
  104. package/src/server/routes/push.ts +0 -54
  105. package/src/services/push-notification.service.ts +0 -104
  106. package/src/web/hooks/use-push-notification.ts +0 -114
  107. /package/dist/web/assets/{api-client-DG9qwosT.js → api-client-BK4NPNoY.js} +0 -0
  108. /package/dist/web/assets/{chevron-down-BMo4cBth.js → chevron-down-CiFNPrfI.js} +0 -0
  109. /package/dist/web/assets/{chevron-right-CD8e6Aj4.js → chevron-right-BzAdxJRG.js} +0 -0
  110. /package/dist/web/assets/{code-DiNmA3eR.js → code-CuravVys.js} +0 -0
  111. /package/dist/web/assets/{csv-parser-B_TuHmnd.js → csv-parser-D1b_lg2T.js} +0 -0
  112. /package/dist/web/assets/{data-grid-types-CO_3iSwd.js → data-grid-types-DzL5W2em.js} +0 -0
  113. /package/dist/web/assets/{database-Dc8mr-dP.js → database-NmqHg29g.js} +0 -0
  114. /package/dist/web/assets/{dist-D4dFaZkK.js → dist-BM2EHhLH.js} +0 -0
  115. /package/dist/web/assets/{dist-C1jciI67.js → dist-CohudVKa.js} +0 -0
  116. /package/dist/web/assets/{file-exclamation-point-B__2Hrd6.js → file-exclamation-point-Baz81y5z.js} +0 -0
  117. /package/dist/web/assets/{katex-DveWxdWJ.js → katex-CHaeM9QC.js} +0 -0
  118. /package/dist/web/assets/{lib-D4YDpYv4.js → lib-LPmTkMu4.js} +0 -0
  119. /package/dist/web/assets/{react-BXxixfbh.js → react-DHBl6KRc.js} +0 -0
  120. /package/dist/web/assets/{search-D90WJ5fo.js → search-BEy08Exr.js} +0 -0
  121. /package/dist/web/assets/{shield-check-DeIMQtEj.js → shield-check-77W0OMbn.js} +0 -0
  122. /package/dist/web/assets/{shield-off-D4jBmG5E.js → shield-off-C_MK1u09.js} +0 -0
  123. /package/dist/web/assets/{sparkles-DyeiGE7Q.js → sparkles-CulWHe4c.js} +0 -0
  124. /package/dist/web/assets/{table-DCYlHUNQ.js → table-BzjWcs87.js} +0 -0
  125. /package/dist/web/assets/{text-wrap-B3mYv9Yo.js → text-wrap-DJz9Bgpa.js} +0 -0
  126. /package/dist/web/assets/{trash-2-DkIfBY8d.js → trash-2-D5P4y8p_.js} +0 -0
  127. /package/dist/web/assets/{utils-Bs_TFEQf.js → utils-CSCvNZxE.js} +0 -0
  128. /package/dist/web/assets/{vendor-xterm-BHJtSw6L.js → vendor-xterm-t3d5xZdz.js} +0 -0
  129. /package/dist/web/assets/{wifi-DifNnmbA.js → wifi-CgM9T6HR.js} +0 -0
  130. /package/dist/web/assets/{x-WwAMX3EB.js → x-Dx3jsRgu.js} +0 -0
@@ -1 +0,0 @@
1
- import{V as e}from"./vendor-mermaid-D2cOxeao.js";export{e as createInfoServices};
@@ -1 +0,0 @@
1
- import"./vendor-markdown-0Mxgxy0L.js";import"./api-client-DG9qwosT.js";import{D as e}from"./index-BS3CQIT3.js";export{e as useKeybindingsStore};
@@ -1 +0,0 @@
1
- import"./vendor-markdown-0Mxgxy0L.js";import"./api-client-DG9qwosT.js";import{P as e}from"./index-BS3CQIT3.js";export{e as useNotificationStore};
@@ -1 +0,0 @@
1
- import{z as e}from"./vendor-mermaid-D2cOxeao.js";export{e as createPacketServices};
@@ -1 +0,0 @@
1
- import{L as e}from"./vendor-mermaid-D2cOxeao.js";export{e as createPieServices};
@@ -1 +0,0 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{n as r,t as i}from"./globe-CQ8NAYvi.js";import{t as a}from"./wifi-DifNnmbA.js";import{t as o}from"./api-client-DG9qwosT.js";import{G as s,R as c,et as l,lt as u,ut as d}from"./index-BS3CQIT3.js";var f=e(n(),1),p=t();function m(){let[e,t]=(0,f.useState)(``),[n,m]=(0,f.useState)([]),[h,g]=(0,f.useState)(!1),[_,v]=(0,f.useState)(null),[y,b]=(0,f.useState)(null),x=(0,f.useCallback)(async()=>{try{m(await o.get(`/api/preview/tunnels`))}catch(e){console.warn(`[ports] failed to fetch tunnels`,e)}},[]);(0,f.useEffect)(()=>{x();let e=setInterval(x,1e4);return()=>clearInterval(e)},[x]);let S=async e=>{let r=n.find(t=>t.port===e);if(r){window.open(r.url,`_blank`),t(``);return}g(!0),v(null);try{let n=await o.post(`/api/preview/tunnel`,{port:e});window.open(n.url,`_blank`),t(``),await x()}catch(t){v(t.message||`Failed to start tunnel for port ${e}`)}finally{g(!1)}},C=async e=>{try{await o.del(`/api/preview/tunnel/${e}`),await x()}catch(t){c.error(t.message||`Failed to stop tunnel for port ${e}`)}},w=(e,t)=>{navigator.clipboard.writeText(t).then(()=>{b(e),c.success(`URL copied`),setTimeout(()=>b(null),2e3)}).catch(()=>{c.error(`Failed to copy URL`)})};return(0,p.jsxs)(`div`,{className:`flex flex-col h-full w-full bg-background`,children:[(0,p.jsxs)(`div`,{className:`p-4 md:p-6 border-b border-border bg-surface`,children:[(0,p.jsxs)(`div`,{className:`flex items-center gap-2 mb-3`,children:[(0,p.jsx)(a,{className:`size-5 text-primary`}),(0,p.jsx)(`h2`,{className:`text-base font-medium text-text-primary`,children:`Port Forwarding`})]}),(0,p.jsxs)(`form`,{onSubmit:t=>{t.preventDefault();let n=parseInt(e,10);n>=1&&n<=65535?S(n):v(`Port must be 1-65535`)},className:`flex items-center gap-2`,children:[(0,p.jsxs)(`div`,{className:`flex-1 flex items-center gap-2 px-3 py-2.5 rounded-lg bg-background border border-border focus-within:border-primary/50 transition-colors`,children:[(0,p.jsx)(`span`,{className:`text-sm text-text-subtle shrink-0`,children:`localhost:`}),(0,p.jsx)(`input`,{type:`number`,value:e,onChange:e=>{t(e.target.value),v(null)},placeholder:`3000`,min:1,max:65535,className:`flex-1 bg-transparent text-sm text-text-primary outline-none placeholder:text-text-subtle min-w-0 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none`})]}),(0,p.jsx)(`button`,{type:`submit`,disabled:h||!e,className:`px-4 py-2.5 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 disabled:opacity-50 transition-colors shrink-0 min-w-[72px] flex items-center justify-center`,children:h?(0,p.jsx)(l,{className:`size-4 animate-spin`}):`Forward`})]}),_&&(0,p.jsx)(`p`,{className:`text-sm text-red-400 mt-2`,children:_}),h&&(0,p.jsxs)(`div`,{className:`flex items-center gap-2 text-sm text-text-secondary mt-2`,children:[(0,p.jsx)(l,{className:`size-3.5 animate-spin`}),(0,p.jsx)(`span`,{children:`Starting tunnel...`})]})]}),(0,p.jsx)(`div`,{className:`flex-1 overflow-y-auto p-4 md:p-6`,children:n.length===0?(0,p.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-center`,children:[(0,p.jsx)(i,{className:`size-10 text-text-subtle`}),(0,p.jsx)(`p`,{className:`text-sm text-text-secondary max-w-xs`,children:`No active ports. Forward a port to access your local dev server from anywhere.`})]}):(0,p.jsx)(`div`,{className:`space-y-2`,children:n.map(e=>(0,p.jsxs)(`div`,{className:`flex items-center gap-3 p-3 rounded-lg bg-surface border border-border`,children:[(0,p.jsxs)(`div`,{className:`shrink-0 px-2 py-1 rounded bg-primary/10 text-primary text-xs font-mono font-medium`,children:[`:`,e.port]}),(0,p.jsx)(`span`,{className:`flex-1 text-xs text-text-secondary truncate min-w-0`,children:e.url}),(0,p.jsxs)(`div`,{className:`flex items-center shrink-0`,children:[(0,p.jsx)(`button`,{onClick:()=>window.open(e.url,`_blank`),className:`p-2.5 rounded-md hover:bg-surface-elevated transition-colors`,title:`Open in browser`,children:(0,p.jsx)(u,{className:`size-4 text-text-secondary`})}),(0,p.jsx)(`button`,{onClick:()=>w(e.port,e.url),className:`p-2.5 rounded-md hover:bg-surface-elevated transition-colors`,title:`Copy URL`,children:y===e.port?(0,p.jsx)(r,{className:`size-4 text-green-400`}):(0,p.jsx)(d,{className:`size-4 text-text-secondary`})}),(0,p.jsx)(`button`,{onClick:()=>C(e.port),className:`p-2.5 rounded-md hover:bg-red-500/10 transition-colors`,title:`Stop tunnel`,children:(0,p.jsx)(s,{className:`size-4 text-red-400`})})]})]},e.port))})})]})}export{m as PortForwardingTab};
@@ -1 +0,0 @@
1
- import{F as e}from"./vendor-mermaid-D2cOxeao.js";export{e as createRadarServices};
@@ -1 +0,0 @@
1
- import{t as e}from"./createLucideIcon-BjHrJDVb.js";var t=e(`refresh-cw`,[[`path`,{d:`M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8`,key:`v9h5vc`}],[`path`,{d:`M21 3v5h-5`,key:`1q7to0`}],[`path`,{d:`M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16`,key:`3uifl3`}],[`path`,{d:`M8 16H3v5`,key:`1cv678`}]]);export{t};
@@ -1 +0,0 @@
1
- import"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import"./ai-settings-section-D0VMZ4aE.js";import"./input-DSELw5zU.js";import"./api-client-DG9qwosT.js";import"./settings-store-MXJgFUnl.js";import"./vendor-mermaid-D2cOxeao.js";import"./project-store-CpC02pIv.js";import"./api-settings-Byph7lae.js";import{c as e}from"./index-BS3CQIT3.js";export{e as SettingsTab};
@@ -1,3 +0,0 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{i as r,r as i}from"./glide-data-grid-Benw7NI4.js";import{n as a,t as o}from"./use-monaco-theme-CbzQcrwD.js";var s=e(n(),1),c=t();function l(e,t){let n=e.split(`
2
- `),r=0;for(let e=0;e<n.length;e++){let i=n[e].trim();e<t-1&&i.endsWith(`;`)&&(r=e+1)}let i=n.length-1;for(let e=t-1;e<n.length;e++)if(n[e].trim().endsWith(`;`)){i=e;break}for(;r<=i;){let e=n[r].trim();if(e&&!e.startsWith(`--`))break;r++}return n.slice(r,i+1).join(`
3
- `).trim()}function u({onExecute:e,loading:t,defaultValue:n=`SELECT * FROM `,schemaInfo:u,onSqlChange:d,persistedSql:f}){let[p,m]=(0,s.useState)(()=>f??n),h=(0,s.useRef)(!!f),g=(0,s.useRef)(null),_=(0,s.useRef)(null),v=(0,s.useRef)(null),y=(0,s.useRef)(e);y.current=e;let b=o();(0,s.useEffect)(()=>{if(!(!_.current||!u))return v.current?.dispose(),i(),v.current=_.current.languages.registerCompletionItemProvider(`sql`,r(_.current,u)),()=>{v.current?.dispose()}},[u]);let x=(0,s.useCallback)((e,t)=>{g.current=e,_.current=t,e.addAction({id:`run-query-at-cursor`,label:`Run Statement at Cursor`,keybindings:[t.KeyMod.CtrlCmd|t.KeyCode.Enter],run:e=>{let t=e.getPosition();if(!t)return;let n=l(e.getValue(),t.lineNumber);n&&y.current(n)}}),u&&(v.current?.dispose(),v.current=t.languages.registerCompletionItemProvider(`sql`,r(t,u)))},[u]);return(0,s.useEffect)(()=>{h.current||m(n)},[n]),(0,c.jsx)(`div`,{className:`h-full overflow-hidden`,children:(0,c.jsx)(a,{height:`100%`,language:`sql`,theme:b,value:p,onChange:e=>{let t=e??``;m(t),h.current=!0,d?.(t)},onMount:x,options:{minimap:{enabled:!1},lineNumbers:`off`,scrollBeyondLastLine:!1,wordWrap:`on`,fontSize:12,tabSize:2,renderLineHighlight:`none`,overviewRulerLanes:0,hideCursorInOverviewRuler:!0,scrollbar:{vertical:`auto`,horizontal:`auto`,verticalScrollbarSize:6,horizontalScrollbarSize:6},padding:{top:4,bottom:4},lineDecorationsWidth:4,lineNumbersMinChars:0,glyphMargin:!1,folding:!1,fixedOverflowWidgets:!0}})})}export{u as t};
@@ -1 +0,0 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{t as r}from"./database-Dc8mr-dP.js";import{t as i}from"./glide-data-grid-Benw7NI4.js";import{t as a}from"./refresh-cw-CRD2qr4U.js";import{t as o}from"./table-DCYlHUNQ.js";import{i as s,t as c}from"./api-client-DG9qwosT.js";import"./settings-store-MXJgFUnl.js";import"./vendor-mermaid-D2cOxeao.js";import"./panel-store-DlvwzOll.js";import"./tab-store-Bdw8DIbZ.js";import{et as l,mt as u}from"./index-BS3CQIT3.js";import"./data-grid-types-CO_3iSwd.js";import"./use-monaco-theme-CbzQcrwD.js";import{t as d}from"./sql-query-editor-DCx7kPlY.js";var f=e(n(),1);function p(e,t,n){let[r,i]=(0,f.useState)([]),[a,o]=(0,f.useState)(null),[l,u]=(0,f.useState)(null),[d,p]=(0,f.useState)([]),[m,h]=(0,f.useState)(!1),[g,_]=(0,f.useState)(null),[v,y]=(0,f.useState)(1),[b,x]=(0,f.useState)(null),[S,C]=(0,f.useState)(null),[w,T]=(0,f.useState)(!1),E=n?`/api/db/connections/${n}`:null,D=E??`${s(e)}/sqlite`,O=E?``:`path=${encodeURIComponent(t)}`,k=(0,f.useCallback)(async()=>{h(!0),_(null);try{let e=E?`?cached=1`:O?`?${O}`:``,t=await c.get(`${D}/tables${e}`);i(t),!E&&t.length>0&&!a&&o(t[0].name)}catch(e){_(e.message)}finally{h(!1)}},[D,O,E]);(0,f.useEffect)(()=>{k()},[k]);let A=(0,f.useCallback)(async()=>{if(a){h(!0);try{let e=O?`${O}&`:``,[t,n]=await Promise.all([c.get(`${D}/data?${e}table=${encodeURIComponent(a)}&page=${v}&limit=100`),c.get(`${D}/schema?${e}table=${encodeURIComponent(a)}`)]);u(t),p(n)}catch(e){_(e.message)}finally{h(!1)}}},[D,O,a,v]);return(0,f.useEffect)(()=>{A()},[A]),{tables:r,selectedTable:a,selectTable:(0,f.useCallback)(e=>{o(e),y(1),x(null)},[]),tableData:l,schema:d,loading:m,error:g,page:v,setPage:y,queryResult:b,queryError:S,queryLoading:w,executeQuery:(0,f.useCallback)(async e=>{T(!0),C(null);try{let n=E?{sql:e}:{path:t,sql:e},r=await c.post(`${D}/query`,n);x(r),r.changeType===`modify`&&A()}catch(e){C(e.message)}finally{T(!1)}},[D,E,t,A]),updateCell:(0,f.useCallback)(async(e,n,r)=>{if(a)try{E?await c.put(`${D}/cell`,{table:a,pkColumn:`rowid`,pkValue:e,column:n,value:r}):await c.put(`${D}/cell`,{path:t,table:a,rowid:e,column:n,value:r}),A()}catch(e){_(e.message)}},[D,E,t,a,A]),deleteRow:(0,f.useCallback)(async e=>{if(a)try{E?await c.del(`${D}/row`,{table:a,pkColumn:`rowid`,pkValue:e}):await c.del(`${D}/row`,{path:t,table:a,rowid:e}),A(),k()}catch(e){_(e.message)}},[D,E,t,a,A,k]),refreshTables:k,refreshData:A}}var m=t();function h({tables:e,selectedTable:t,onSelect:n,onRefresh:r}){return(0,m.jsxs)(`div`,{className:`w-48 shrink-0 flex flex-col bg-background overflow-hidden`,children:[(0,m.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-2 border-b border-border`,children:[(0,m.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground uppercase tracking-wider`,children:`Tables`}),(0,m.jsx)(`button`,{type:`button`,onClick:r,className:`text-muted-foreground hover:text-foreground transition-colors`,title:`Refresh tables`,children:(0,m.jsx)(a,{className:`size-3`})})]}),(0,m.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[e.map(e=>(0,m.jsxs)(`button`,{type:`button`,onClick:()=>n(e.name),className:`w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors ${t===e.name?`bg-muted text-foreground`:`text-muted-foreground hover:bg-muted/50 hover:text-foreground`}`,children:[(0,m.jsx)(o,{className:`size-3 shrink-0`}),(0,m.jsx)(`span`,{className:`truncate flex-1`,children:e.name}),(0,m.jsx)(`span`,{className:`text-[10px] opacity-60`,children:e.rowCount})]},e.name)),e.length===0&&(0,m.jsx)(`p`,{className:`px-3 py-4 text-xs text-muted-foreground text-center`,children:`No tables found`})]})]})}function g({onExecute:e,loading:t}){return(0,m.jsx)(d,{onExecute:e,loading:t})}function _({metadata:e}){let t=e?.filePath,n=e?.projectName,i=e?.connectionId,a=e?.tableName,[o,s]=(0,f.useState)(!1);return i?(0,m.jsx)(v,{projectName:``,dbPath:``,connectionId:i,connectionName:e?.connectionName,initialTable:a,queryPanelOpen:o,onToggleQueryPanel:()=>s(e=>!e),hideTableList:!0}):!t||!n?(0,m.jsxs)(`div`,{className:`flex items-center justify-center h-full text-text-secondary text-sm`,children:[(0,m.jsx)(r,{className:`size-5 mr-2`}),` No database file selected.`]}):(0,m.jsx)(v,{projectName:n,dbPath:t,queryPanelOpen:o,onToggleQueryPanel:()=>s(e=>!e)})}function v({projectName:e,dbPath:t,connectionId:n,connectionName:a,initialTable:o,queryPanelOpen:s,onToggleQueryPanel:c,hideTableList:d}){let _=p(e,t,n),v=(0,f.useRef)(!1);return(0,f.useEffect)(()=>{o&&!v.current&&_.tables.length>0&&(v.current=!0,_.selectTable(o))},[o,_.tables]),_.error&&_.tables.length===0?(0,m.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,m.jsx)(u,{className:`size-10 text-destructive`}),(0,m.jsx)(`p`,{className:`text-sm`,children:_.error})]}):_.loading&&_.tables.length===0?(0,m.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-text-secondary`,children:[(0,m.jsx)(l,{className:`size-5 animate-spin`}),(0,m.jsx)(`span`,{className:`text-sm`,children:`Loading database...`})]}):(0,m.jsxs)(`div`,{className:`flex h-full w-full overflow-hidden`,children:[!d&&(0,m.jsx)(h,{tables:_.tables,selectedTable:_.selectedTable,onSelect:_.selectTable,onRefresh:_.refreshTables}),(0,m.jsxs)(`div`,{className:`flex-1 flex flex-col overflow-hidden ${d?``:`border-l border-border`}`,children:[(0,m.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,m.jsx)(r,{className:`size-3.5 text-muted-foreground`}),(0,m.jsx)(`span`,{className:`text-xs text-muted-foreground truncate`,children:a??t}),(0,m.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:_.selectedTable&&`/ ${_.selectedTable}`}),(0,m.jsx)(`div`,{className:`ml-auto`,children:(0,m.jsx)(`button`,{type:`button`,onClick:c,className:`px-2 py-1 rounded text-xs transition-colors ${s?`bg-muted text-foreground`:`text-muted-foreground hover:text-foreground`}`,children:`SQL`})})]}),(0,m.jsx)(`div`,{className:`flex-1 overflow-hidden ${s?`max-h-[60%]`:``}`,children:_.tableData?(0,m.jsx)(i,{columns:_.tableData.columns,rows:_.tableData.rows,total:_.tableData.total,limit:_.tableData.limit,schema:_.schema.map(e=>({name:e.name,type:e.type,nullable:!e.notnull,pk:!!e.pk,defaultValue:e.dflt_value,fk:e.fk??null})),loading:_.loading,page:_.page,onPageChange:_.setPage,onCellUpdate:(e,t,n,r)=>_.updateCell(t,n,r),onRowDelete:(e,t)=>_.deleteRow(t)}):(0,m.jsx)(`div`,{className:`flex items-center justify-center h-full text-xs text-muted-foreground`,children:_.loading?(0,m.jsx)(l,{className:`size-4 animate-spin`}):`Select a table`})}),s&&(0,m.jsx)(`div`,{className:`border-t border-border h-[40%] shrink-0`,children:(0,m.jsx)(g,{onExecute:_.executeQuery,loading:_.queryLoading})})]})]})}export{_ as SqliteViewer};
@@ -1 +0,0 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import"./vendor-ui-UXCWAcmi.js";import{t as r}from"./createLucideIcon-BjHrJDVb.js";import{t as i}from"./arrow-down-D825m4vm.js";import{t as a}from"./arrow-up-Rcw6_KKu.js";import{n as o,t as s}from"./chevron-down-BMo4cBth.js";import{t as c}from"./chevron-right-CD8e6Aj4.js";import{t as l}from"./wifi-DifNnmbA.js";import{t as u}from"./x-WwAMX3EB.js";import{t as d}from"./api-client-DG9qwosT.js";import{n as f}from"./utils-Bs_TFEQf.js";import{B as p,J as m,R as h,Z as g,a as _,g as v}from"./index-BS3CQIT3.js";var y=r(`circle-question-mark`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3`,key:`1u773s`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),b=r(`hammer`,[[`path`,{d:`m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9`,key:`1hayfq`}],[`path`,{d:`m18 15 4-4`,key:`16gjal`}],[`path`,{d:`m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172v-.344a2 2 0 0 0-.586-1.414l-1.657-1.657A6 6 0 0 0 12.516 3H9l1.243 1.243A6 6 0 0 1 12 8.485V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5`,key:`15ts47`}]]),x=e(n(),1),S=t(),C=(0,x.memo)(function({data:e,width:t=120,height:n=24,color:r=`#3b82f6`}){let i=(0,x.useRef)(null),a=(0,x.useRef)(0);return(0,x.useEffect)(()=>{let o=Date.now();if(o-a.current<1e3)return;a.current=o;let s=i.current;if(!s||e.length<2)return;let c=s.getContext(`2d`);if(!c)return;let l=window.devicePixelRatio||1;s.width=t*l,s.height=n*l,c.scale(l,l),c.clearRect(0,0,t,n);let u=Math.max(...e,1),d=t/(e.length-1),f=n-4;c.beginPath(),c.strokeStyle=r,c.lineWidth=1.5,c.lineJoin=`round`;for(let t=0;t<e.length;t++){let n=t*d,r=2+f-e[t]/u*f;t===0?c.moveTo(n,r):c.lineTo(n,r)}c.stroke()},[e,t,n,r]),(0,S.jsx)(`canvas`,{ref:i,style:{width:t,height:n},className:`inline-block`})});function w(e){return e>80?`text-red-500`:e>50?`text-yellow-500`:`text-green-500`}function T(e){return e<1024?`${e.toFixed(0)} MB`:`${(e/1024).toFixed(1)} GB`}function E(e){if(!e)return``;let t=Math.round((Date.now()-e)/1e3);if(t<60)return`${t}s`;let n=Math.floor(t/60);if(n<60)return`${n}m`;let r=Math.floor(n/60);return r<24?`${r}h ${n%60}m`:`${Math.floor(r/24)}d ${r%24}h`}var D=(0,x.memo)(function({group:e,Icon:t,isExpanded:n,sparkData:r,isMobile:i,sortKey:a,sortDir:o,onToggle:l,onKill:d}){let p=n?s:c,m=(0,x.useMemo)(()=>a?[...e.processes].sort((e,t)=>{let n=a===`cpu`?e.cpu:e.ramMB,r=a===`cpu`?t.cpu:t.ramMB;return o===`asc`?n-r:r-n}):e.processes,[e.processes,a,o]);return(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`tr`,{className:`hover:bg-surface-hover cursor-pointer transition-colors`,onClick:l,children:[(0,S.jsx)(`td`,{className:`py-1.5 px-3`,children:(0,S.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,S.jsx)(p,{className:`size-3 text-text-subtle shrink-0`}),(0,S.jsx)(t,{className:`size-3.5 text-text-secondary shrink-0`}),(0,S.jsx)(`span`,{className:`truncate`,children:e.label}),(0,S.jsxs)(`span`,{className:`text-text-subtle`,children:[`(`,e.processes.length,`)`]})]})}),(0,S.jsxs)(`td`,{className:f(`text-right py-1.5 px-2`,w(e.cpu)),children:[e.cpu.toFixed(1),`%`]}),(0,S.jsx)(`td`,{className:`text-right py-1.5 px-2 text-text-secondary`,children:T(e.ramMB)}),!i&&(0,S.jsx)(`td`,{className:`py-1.5 px-2`,children:r.length>1&&(0,S.jsx)(C,{data:r,width:120,height:20})})]}),n&&m.map(e=>(0,S.jsxs)(`tr`,{className:`text-text-subtle group/proc hover:bg-surface-hover transition-colors`,children:[(0,S.jsx)(`td`,{className:`py-1 px-3 pl-10`,children:(0,S.jsxs)(`div`,{className:`flex items-start gap-1.5`,children:[(0,S.jsx)(`span`,{className:`text-text-subtle shrink-0`,children:e.pid}),(0,S.jsx)(`span`,{className:`break-all text-text-secondary`,title:e.command,children:e.command})]})}),(0,S.jsxs)(`td`,{className:`text-right py-1 px-2 align-top`,children:[e.cpu.toFixed(1),`%`]}),(0,S.jsx)(`td`,{className:`text-right py-1 px-2 align-top`,children:T(e.ramMB)}),!i&&(0,S.jsx)(`td`,{className:`align-top py-1 px-2`,children:(0,S.jsxs)(`div`,{className:`flex items-center justify-between gap-1`,children:[(0,S.jsx)(`span`,{className:`text-text-subtle`,title:e.startedAt?new Date(e.startedAt).toLocaleString():``,children:E(e.startedAt)}),(0,S.jsx)(`button`,{onClick:t=>{t.stopPropagation(),d(e.pid)},className:`opacity-0 group-hover/proc:opacity-100 p-0.5 rounded hover:bg-red-500/20 hover:text-red-500 transition-all`,title:`End process ${e.pid}`,children:(0,S.jsx)(u,{className:`size-3`})})]})})]},e.pid))]})});function O({label:e,field:t,activeKey:n,activeDir:r,onClick:o,className:s}){let c=n===t,l=c?r===`asc`?a:i:null;return(0,S.jsx)(`th`,{className:f(`text-right py-1.5 px-2 font-medium cursor-pointer select-none hover:text-text-primary transition-colors`,c&&`text-text-primary`,s),onClick:()=>o(t),children:(0,S.jsxs)(`div`,{className:`flex items-center justify-end gap-0.5`,children:[(0,S.jsx)(`span`,{children:e}),l&&(0,S.jsx)(l,{className:`size-3`})]})})}var k={server:m,terminal:g,"ai-tool":o,build:b,unknown:y},A=200;function j(e,t,n){return e===n?t===`desc`?[n,`asc`]:[null,`desc`]:[n,`desc`]}function M(e,t,n){return t?[...e].sort((e,r)=>{let i=t===`cpu`?e.cpu:e.ramMB,a=t===`cpu`?r.cpu:r.ramMB;return n===`asc`?i-a:a-i}):e}var N=(0,x.memo)(function(){let{latest:e,history:t,isConnected:n}=_(),[r,i]=(0,x.useState)(()=>new Set([`server`])),[a,o]=(0,x.useState)(null),[s,c]=(0,x.useState)(`desc`),u=v(),m=(0,x.useMemo)(()=>{let e=new Map,n=t.slice(-A);for(let t of n)for(let n of t.groups){let t=`${n.type}:${n.label}`,r=e.get(t)??[];r.push(n.cpu),e.set(t,r)}return e},[t]),g=(0,x.useMemo)(()=>e?M(e.groups,a,s):[],[e,a,s]),b=(0,x.useCallback)(async e=>{try{await d.post(`/api/system/resources/kill/${e}`),h.success(`Sent SIGTERM to PID ${e}`)}catch(t){h.error(t.message||`Failed to kill PID ${e}`)}},[]),C=e=>{i(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},E=e=>{let[t,n]=j(a,s,e);o(t),c(n)};if(!e)return(0,S.jsx)(`div`,{className:`flex items-center justify-center h-full text-text-subtle text-sm`,children:n?`Waiting for data...`:`Connecting to resource monitor...`});let N=Math.round((Date.now()-e.timestamp)/1e3);return(0,S.jsxs)(`div`,{className:`h-full flex flex-col overflow-hidden`,children:[(0,S.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-2 border-b border-border shrink-0`,children:[(0,S.jsx)(`h2`,{className:`text-sm font-medium`,children:`System Monitor`}),(0,S.jsxs)(`div`,{className:`flex items-center gap-1.5 text-[10px] text-text-subtle`,children:[n?(0,S.jsx)(l,{className:`size-3 text-green-500`}):(0,S.jsx)(p,{className:`size-3 text-red-500`}),(0,S.jsx)(`span`,{children:n?`Updated ${N}s ago`:`Disconnected`})]})]}),(0,S.jsx)(`div`,{className:`flex-1 overflow-y-auto min-h-0`,children:(0,S.jsxs)(`table`,{className:`w-full text-xs`,children:[(0,S.jsx)(`thead`,{children:(0,S.jsxs)(`tr`,{className:`text-text-subtle border-b border-border`,children:[(0,S.jsx)(`th`,{className:`text-left py-1.5 px-3 font-medium`,children:`Process`}),(0,S.jsx)(O,{label:`CPU`,field:`cpu`,activeKey:a,activeDir:s,onClick:E,className:`w-16`}),(0,S.jsx)(O,{label:`RAM`,field:`ram`,activeKey:a,activeDir:s,onClick:E,className:`w-20`}),!u&&(0,S.jsx)(`th`,{className:`py-1.5 px-2 font-medium w-[130px]`,children:`Trend / Age`})]})}),(0,S.jsx)(`tbody`,{children:g.map(e=>{let t=`${e.type}:${e.label}`;return(0,S.jsx)(D,{group:e,Icon:k[e.type]??y,isExpanded:r.has(t),sparkData:m.get(t)??[],isMobile:u,sortKey:a,sortDir:s,onToggle:()=>C(t),onKill:b},t)})}),(0,S.jsx)(`tfoot`,{children:(0,S.jsxs)(`tr`,{className:`border-t border-border font-medium`,children:[(0,S.jsxs)(`td`,{className:`py-1.5 px-3`,children:[`Total (`,e.total.processCount,` processes)`]}),(0,S.jsxs)(`td`,{className:f(`text-right py-1.5 px-2`,w(e.total.cpu)),children:[e.total.cpu.toFixed(1),`%`]}),(0,S.jsx)(`td`,{className:`text-right py-1.5 px-2 text-text-secondary`,children:T(e.total.ramMB)}),!u&&(0,S.jsx)(`td`,{})]})})]})})]})});export{N as SystemMonitorTab};
@@ -1 +0,0 @@
1
- import{N as e}from"./vendor-mermaid-D2cOxeao.js";export{e as createTreemapServices};
@@ -1,54 +0,0 @@
1
- import { Hono } from "hono";
2
- import { pushService } from "../../services/push-notification.service.ts";
3
- import { ok, err } from "../../types/api.ts";
4
-
5
- export const pushRoutes = new Hono();
6
-
7
- /** GET /push/vapid-key — return VAPID public key for frontend subscription */
8
- pushRoutes.get("/vapid-key", (c) => {
9
- try {
10
- const publicKey = pushService.getVapidPublicKey();
11
- return c.json(ok({ publicKey }));
12
- } catch (e) {
13
- return c.json(err((e as Error).message), 500);
14
- }
15
- });
16
-
17
- /** POST /push/subscribe — save a push subscription */
18
- pushRoutes.post("/subscribe", async (c) => {
19
- try {
20
- const body = await c.req.json<{
21
- endpoint?: string;
22
- keys?: { p256dh?: string; auth?: string };
23
- expirationTime?: number | null;
24
- }>();
25
-
26
- if (!body.endpoint || !body.keys?.p256dh || !body.keys?.auth) {
27
- return c.json(err("Invalid subscription: missing endpoint or keys"), 400);
28
- }
29
-
30
- pushService.saveSubscription({
31
- endpoint: body.endpoint,
32
- keys: { p256dh: body.keys.p256dh, auth: body.keys.auth },
33
- expirationTime: body.expirationTime,
34
- });
35
-
36
- return c.json(ok(true));
37
- } catch (e) {
38
- return c.json(err((e as Error).message), 400);
39
- }
40
- });
41
-
42
- /** DELETE /push/subscribe — remove a push subscription by endpoint */
43
- pushRoutes.delete("/subscribe", async (c) => {
44
- try {
45
- const body = await c.req.json<{ endpoint?: string }>();
46
- if (!body.endpoint) {
47
- return c.json(err("Missing endpoint"), 400);
48
- }
49
- pushService.removeSubscription(body.endpoint);
50
- return c.json(ok(true));
51
- } catch (e) {
52
- return c.json(err((e as Error).message), 400);
53
- }
54
- });
@@ -1,104 +0,0 @@
1
- import webpush from "web-push";
2
- import { configService } from "./config.service.ts";
3
- import type { PushConfig } from "../types/config.ts";
4
- import {
5
- getPushSubscriptions,
6
- upsertPushSubscription,
7
- deletePushSubscription,
8
- } from "./db.service.ts";
9
-
10
- interface PushSubscriptionData {
11
- endpoint: string;
12
- keys: { p256dh: string; auth: string };
13
- expirationTime?: number | null;
14
- }
15
-
16
- class PushNotificationService {
17
- private initialized = false;
18
-
19
- /** Initialize VAPID keys — auto-generate if missing */
20
- init(): void {
21
- if (this.initialized) return;
22
-
23
- let push = configService.get("push") as PushConfig | undefined;
24
- if (!push?.vapid_public_key || !push?.vapid_private_key) {
25
- const keys = webpush.generateVAPIDKeys();
26
- push = {
27
- vapid_public_key: keys.publicKey,
28
- vapid_private_key: keys.privateKey,
29
- vapid_subject: "https://ppm.local",
30
- };
31
- configService.set("push", push);
32
- configService.save();
33
- console.log("[push] VAPID keys generated and saved to config");
34
- }
35
-
36
- webpush.setVapidDetails(
37
- push.vapid_subject,
38
- push.vapid_public_key,
39
- push.vapid_private_key,
40
- );
41
- this.initialized = true;
42
- }
43
-
44
- /** Get VAPID public key for frontend subscription */
45
- getVapidPublicKey(): string {
46
- this.init();
47
- const push = configService.get("push") as PushConfig | undefined;
48
- return push?.vapid_public_key ?? "";
49
- }
50
-
51
- /** Save a new push subscription */
52
- saveSubscription(sub: PushSubscriptionData): void {
53
- upsertPushSubscription(
54
- sub.endpoint,
55
- sub.keys.p256dh,
56
- sub.keys.auth,
57
- sub.expirationTime != null ? String(sub.expirationTime) : null,
58
- );
59
- }
60
-
61
- /** Remove a push subscription by endpoint */
62
- removeSubscription(endpoint: string): void {
63
- deletePushSubscription(endpoint);
64
- }
65
-
66
- /** Send push notification to all subscriptions (fire-and-forget) */
67
- async notifyAll(title: string, body: string): Promise<void> {
68
- this.init();
69
- const dbSubs = getPushSubscriptions();
70
- if (dbSubs.length === 0) return;
71
-
72
- const payload = JSON.stringify({ title, body });
73
- const expired: string[] = [];
74
-
75
- const subs: PushSubscriptionData[] = dbSubs.map((r) => ({
76
- endpoint: r.endpoint,
77
- keys: { p256dh: r.p256dh, auth: r.auth },
78
- expirationTime: r.expiration_time ? Number(r.expiration_time) : null,
79
- }));
80
-
81
- await Promise.allSettled(
82
- subs.map(async (sub) => {
83
- try {
84
- await webpush.sendNotification(sub, payload);
85
- } catch (error: unknown) {
86
- const statusCode = (error as { statusCode?: number }).statusCode;
87
- if (statusCode === 410 || statusCode === 404) {
88
- expired.push(sub.endpoint);
89
- }
90
- }
91
- }),
92
- );
93
-
94
- for (const endpoint of expired) {
95
- deletePushSubscription(endpoint);
96
- }
97
- if (expired.length > 0) {
98
- console.log(`[push] Removed ${expired.length} expired subscriptions`);
99
- }
100
- }
101
- }
102
-
103
- /** Singleton push notification service */
104
- export const pushService = new PushNotificationService();
@@ -1,114 +0,0 @@
1
- import { useState, useEffect, useCallback } from "react";
2
- import { getAuthToken } from "@/lib/api-client";
3
-
4
- /** Convert VAPID public key from base64url to Uint8Array for PushManager */
5
- function urlBase64ToUint8Array(base64String: string): Uint8Array {
6
- const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
7
- const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
8
- const rawData = atob(base64);
9
- return Uint8Array.from(rawData, (char) => char.charCodeAt(0));
10
- }
11
-
12
- export function usePushNotification() {
13
- const [permission, setPermission] = useState<NotificationPermission>("default");
14
- const [isSubscribed, setIsSubscribed] = useState(false);
15
- const [loading, setLoading] = useState(false);
16
- const [error, setError] = useState<string | null>(null);
17
-
18
- // Check current permission and subscription state on mount
19
- useEffect(() => {
20
- if ("Notification" in window) {
21
- setPermission(Notification.permission);
22
- }
23
- setIsSubscribed(localStorage.getItem("ppm-push-subscribed") === "true");
24
- }, []);
25
-
26
- const subscribe = useCallback(async () => {
27
- setLoading(true);
28
- setError(null);
29
- try {
30
- // 1. Request notification permission
31
- const perm = await Notification.requestPermission();
32
- setPermission(perm);
33
- if (perm !== "granted") {
34
- setError("Permission denied");
35
- return;
36
- }
37
-
38
- // 2. Check service worker is available (with timeout)
39
- if (!navigator.serviceWorker?.controller && !navigator.serviceWorker?.ready) {
40
- setError("Service worker not available (dev mode?)");
41
- return;
42
- }
43
-
44
- const swReady = await Promise.race([
45
- navigator.serviceWorker.ready,
46
- new Promise<null>((_, reject) => setTimeout(() => reject(new Error("Service worker timeout")), 5000)),
47
- ]);
48
- if (!swReady) throw new Error("Service worker not ready");
49
-
50
- // 3. Get VAPID public key from server
51
- const headers: Record<string, string> = {};
52
- const token = getAuthToken();
53
- if (token) headers.Authorization = `Bearer ${token}`;
54
-
55
- const res = await fetch("/api/push/vapid-key", { headers });
56
- const json = await res.json();
57
- if (!json.ok) throw new Error(json.error || "Failed to get VAPID key");
58
-
59
- // 4. Subscribe via PushManager
60
- const sub = await swReady.pushManager.subscribe({
61
- userVisibleOnly: true,
62
- applicationServerKey: urlBase64ToUint8Array(json.data.publicKey).buffer as ArrayBuffer,
63
- });
64
-
65
- // 5. Send subscription to server
66
- await fetch("/api/push/subscribe", {
67
- method: "POST",
68
- headers: { ...headers, "Content-Type": "application/json" },
69
- body: JSON.stringify(sub.toJSON()),
70
- });
71
-
72
- setIsSubscribed(true);
73
- localStorage.setItem("ppm-push-subscribed", "true");
74
- } catch (err) {
75
- const msg = err instanceof Error ? err.message : "Subscribe failed";
76
- setError(msg);
77
- console.error("[push] Subscribe failed:", err);
78
- } finally {
79
- setLoading(false);
80
- }
81
- }, []);
82
-
83
- const unsubscribe = useCallback(async () => {
84
- setLoading(true);
85
- try {
86
- const reg = await navigator.serviceWorker.ready;
87
- const sub = await reg.pushManager.getSubscription();
88
- if (sub) {
89
- // Remove from server
90
- const headers: Record<string, string> = { "Content-Type": "application/json" };
91
- const token = getAuthToken();
92
- if (token) headers.Authorization = `Bearer ${token}`;
93
-
94
- await fetch("/api/push/subscribe", {
95
- method: "DELETE",
96
- headers,
97
- body: JSON.stringify({ endpoint: sub.endpoint }),
98
- });
99
-
100
- // Unsubscribe locally
101
- await sub.unsubscribe();
102
- }
103
-
104
- setIsSubscribed(false);
105
- localStorage.removeItem("ppm-push-subscribed");
106
- } catch (err) {
107
- console.error("[push] Unsubscribe failed:", err);
108
- } finally {
109
- setLoading(false);
110
- }
111
- }, []);
112
-
113
- return { permission, isSubscribed, loading, error, subscribe, unsubscribe };
114
- }