@qwickapps/server 1.2.0 → 1.3.1

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 (299) hide show
  1. package/README.md +392 -0
  2. package/dist/core/control-panel.d.ts +7 -2
  3. package/dist/core/control-panel.d.ts.map +1 -1
  4. package/dist/core/control-panel.js +120 -54
  5. package/dist/core/control-panel.js.map +1 -1
  6. package/dist/core/gateway.d.ts +159 -79
  7. package/dist/core/gateway.d.ts.map +1 -1
  8. package/dist/core/gateway.js +679 -319
  9. package/dist/core/gateway.js.map +1 -1
  10. package/dist/core/index.d.ts +3 -1
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/core/index.js +2 -0
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/plugin-registry.d.ts +307 -0
  15. package/dist/core/plugin-registry.d.ts.map +1 -0
  16. package/dist/core/plugin-registry.js +352 -0
  17. package/dist/core/plugin-registry.js.map +1 -0
  18. package/dist/core/types.d.ts +16 -33
  19. package/dist/core/types.d.ts.map +1 -1
  20. package/dist/index.d.ts +8 -5
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -7
  23. package/dist/index.js.map +1 -1
  24. package/dist/plugins/auth/adapters/auth0-adapter.d.ts +14 -0
  25. package/dist/plugins/auth/adapters/auth0-adapter.d.ts.map +1 -0
  26. package/dist/plugins/auth/adapters/auth0-adapter.js +179 -0
  27. package/dist/plugins/auth/adapters/auth0-adapter.js.map +1 -0
  28. package/dist/plugins/auth/adapters/basic-adapter.d.ts +13 -0
  29. package/dist/plugins/auth/adapters/basic-adapter.d.ts.map +1 -0
  30. package/dist/plugins/auth/adapters/basic-adapter.js +51 -0
  31. package/dist/plugins/auth/adapters/basic-adapter.js.map +1 -0
  32. package/dist/plugins/auth/adapters/index.d.ts +10 -0
  33. package/dist/plugins/auth/adapters/index.d.ts.map +1 -0
  34. package/dist/plugins/auth/adapters/index.js +10 -0
  35. package/dist/plugins/auth/adapters/index.js.map +1 -0
  36. package/dist/plugins/auth/adapters/supabase-adapter.d.ts +13 -0
  37. package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -0
  38. package/dist/plugins/auth/adapters/supabase-adapter.js +109 -0
  39. package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -0
  40. package/dist/plugins/auth/adapters/supertokens-adapter.d.ts +18 -0
  41. package/dist/plugins/auth/adapters/supertokens-adapter.d.ts.map +1 -0
  42. package/dist/plugins/auth/adapters/supertokens-adapter.js +267 -0
  43. package/dist/plugins/auth/adapters/supertokens-adapter.js.map +1 -0
  44. package/dist/plugins/auth/auth-plugin.d.ts +40 -0
  45. package/dist/plugins/auth/auth-plugin.d.ts.map +1 -0
  46. package/dist/plugins/auth/auth-plugin.js +255 -0
  47. package/dist/plugins/auth/auth-plugin.js.map +1 -0
  48. package/dist/plugins/auth/auth-plugin.test.d.ts +9 -0
  49. package/dist/plugins/auth/auth-plugin.test.d.ts.map +1 -0
  50. package/dist/plugins/auth/auth-plugin.test.js +147 -0
  51. package/dist/plugins/auth/auth-plugin.test.js.map +1 -0
  52. package/dist/plugins/auth/env-config.d.ts +88 -0
  53. package/dist/plugins/auth/env-config.d.ts.map +1 -0
  54. package/dist/plugins/auth/env-config.js +489 -0
  55. package/dist/plugins/auth/env-config.js.map +1 -0
  56. package/dist/plugins/auth/index.d.ts +14 -0
  57. package/dist/plugins/auth/index.d.ts.map +1 -0
  58. package/dist/plugins/auth/index.js +16 -0
  59. package/dist/plugins/auth/index.js.map +1 -0
  60. package/dist/plugins/auth/supertokens-adapter.test.d.ts +10 -0
  61. package/dist/plugins/auth/supertokens-adapter.test.d.ts.map +1 -0
  62. package/dist/plugins/auth/supertokens-adapter.test.js +486 -0
  63. package/dist/plugins/auth/supertokens-adapter.test.js.map +1 -0
  64. package/dist/plugins/auth/types.d.ts +218 -0
  65. package/dist/plugins/auth/types.d.ts.map +1 -0
  66. package/dist/plugins/auth/types.js +14 -0
  67. package/dist/plugins/auth/types.js.map +1 -0
  68. package/dist/plugins/bans/bans-plugin.d.ts +59 -0
  69. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -0
  70. package/dist/plugins/bans/bans-plugin.js +428 -0
  71. package/dist/plugins/bans/bans-plugin.js.map +1 -0
  72. package/dist/plugins/bans/index.d.ts +9 -0
  73. package/dist/plugins/bans/index.d.ts.map +1 -0
  74. package/dist/plugins/bans/index.js +10 -0
  75. package/dist/plugins/bans/index.js.map +1 -0
  76. package/dist/plugins/bans/stores/index.d.ts +7 -0
  77. package/dist/plugins/bans/stores/index.d.ts.map +1 -0
  78. package/dist/plugins/bans/stores/index.js +7 -0
  79. package/dist/plugins/bans/stores/index.js.map +1 -0
  80. package/dist/plugins/bans/stores/postgres-store.d.ts +29 -0
  81. package/dist/plugins/bans/stores/postgres-store.d.ts.map +1 -0
  82. package/dist/plugins/bans/stores/postgres-store.js +132 -0
  83. package/dist/plugins/bans/stores/postgres-store.js.map +1 -0
  84. package/dist/plugins/bans/types.d.ts +128 -0
  85. package/dist/plugins/bans/types.d.ts.map +1 -0
  86. package/dist/plugins/bans/types.js +11 -0
  87. package/dist/plugins/bans/types.js.map +1 -0
  88. package/dist/plugins/cache-plugin.d.ts +14 -3
  89. package/dist/plugins/cache-plugin.d.ts.map +1 -1
  90. package/dist/plugins/cache-plugin.js +27 -7
  91. package/dist/plugins/cache-plugin.js.map +1 -1
  92. package/dist/plugins/cache-plugin.test.js +99 -32
  93. package/dist/plugins/cache-plugin.test.js.map +1 -1
  94. package/dist/plugins/config-plugin.d.ts +3 -2
  95. package/dist/plugins/config-plugin.d.ts.map +1 -1
  96. package/dist/plugins/config-plugin.js +17 -10
  97. package/dist/plugins/config-plugin.js.map +1 -1
  98. package/dist/plugins/diagnostics-plugin.d.ts +2 -2
  99. package/dist/plugins/diagnostics-plugin.d.ts.map +1 -1
  100. package/dist/plugins/diagnostics-plugin.js +17 -10
  101. package/dist/plugins/diagnostics-plugin.js.map +1 -1
  102. package/dist/plugins/entitlements/entitlements-plugin.d.ts +95 -0
  103. package/dist/plugins/entitlements/entitlements-plugin.d.ts.map +1 -0
  104. package/dist/plugins/entitlements/entitlements-plugin.js +707 -0
  105. package/dist/plugins/entitlements/entitlements-plugin.js.map +1 -0
  106. package/dist/plugins/entitlements/index.d.ts +12 -0
  107. package/dist/plugins/entitlements/index.d.ts.map +1 -0
  108. package/dist/plugins/entitlements/index.js +16 -0
  109. package/dist/plugins/entitlements/index.js.map +1 -0
  110. package/dist/plugins/entitlements/sources/index.d.ts +9 -0
  111. package/dist/plugins/entitlements/sources/index.d.ts.map +1 -0
  112. package/dist/plugins/entitlements/sources/index.js +9 -0
  113. package/dist/plugins/entitlements/sources/index.js.map +1 -0
  114. package/dist/plugins/entitlements/sources/postgres-source.d.ts +29 -0
  115. package/dist/plugins/entitlements/sources/postgres-source.d.ts.map +1 -0
  116. package/dist/plugins/entitlements/sources/postgres-source.js +169 -0
  117. package/dist/plugins/entitlements/sources/postgres-source.js.map +1 -0
  118. package/dist/plugins/entitlements/types.d.ts +232 -0
  119. package/dist/plugins/entitlements/types.d.ts.map +1 -0
  120. package/dist/plugins/entitlements/types.js +11 -0
  121. package/dist/plugins/entitlements/types.js.map +1 -0
  122. package/dist/plugins/frontend-app-plugin.d.ts +9 -3
  123. package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
  124. package/dist/plugins/frontend-app-plugin.js +14 -9
  125. package/dist/plugins/frontend-app-plugin.js.map +1 -1
  126. package/dist/plugins/health-plugin.d.ts +5 -2
  127. package/dist/plugins/health-plugin.d.ts.map +1 -1
  128. package/dist/plugins/health-plugin.js +20 -5
  129. package/dist/plugins/health-plugin.js.map +1 -1
  130. package/dist/plugins/index.d.ts +10 -2
  131. package/dist/plugins/index.d.ts.map +1 -1
  132. package/dist/plugins/index.js +10 -2
  133. package/dist/plugins/index.js.map +1 -1
  134. package/dist/plugins/logs-plugin.d.ts +3 -2
  135. package/dist/plugins/logs-plugin.d.ts.map +1 -1
  136. package/dist/plugins/logs-plugin.js +21 -12
  137. package/dist/plugins/logs-plugin.js.map +1 -1
  138. package/dist/plugins/postgres-plugin.d.ts +3 -3
  139. package/dist/plugins/postgres-plugin.d.ts.map +1 -1
  140. package/dist/plugins/postgres-plugin.js +9 -7
  141. package/dist/plugins/postgres-plugin.js.map +1 -1
  142. package/dist/plugins/postgres-plugin.test.js +50 -29
  143. package/dist/plugins/postgres-plugin.test.js.map +1 -1
  144. package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts +7 -0
  145. package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts.map +1 -0
  146. package/dist/plugins/preferences/__tests__/deep-merge.test.js +215 -0
  147. package/dist/plugins/preferences/__tests__/deep-merge.test.js.map +1 -0
  148. package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts +7 -0
  149. package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts.map +1 -0
  150. package/dist/plugins/preferences/__tests__/preferences-plugin.test.js +265 -0
  151. package/dist/plugins/preferences/__tests__/preferences-plugin.test.js.map +1 -0
  152. package/dist/plugins/preferences/index.d.ts +12 -0
  153. package/dist/plugins/preferences/index.d.ts.map +1 -0
  154. package/dist/plugins/preferences/index.js +13 -0
  155. package/dist/plugins/preferences/index.js.map +1 -0
  156. package/dist/plugins/preferences/preferences-plugin.d.ts +39 -0
  157. package/dist/plugins/preferences/preferences-plugin.d.ts.map +1 -0
  158. package/dist/plugins/preferences/preferences-plugin.js +226 -0
  159. package/dist/plugins/preferences/preferences-plugin.js.map +1 -0
  160. package/dist/plugins/preferences/stores/index.d.ts +9 -0
  161. package/dist/plugins/preferences/stores/index.d.ts.map +1 -0
  162. package/dist/plugins/preferences/stores/index.js +9 -0
  163. package/dist/plugins/preferences/stores/index.js.map +1 -0
  164. package/dist/plugins/preferences/stores/postgres-store.d.ts +41 -0
  165. package/dist/plugins/preferences/stores/postgres-store.d.ts.map +1 -0
  166. package/dist/plugins/preferences/stores/postgres-store.js +181 -0
  167. package/dist/plugins/preferences/stores/postgres-store.js.map +1 -0
  168. package/dist/plugins/preferences/types.d.ts +91 -0
  169. package/dist/plugins/preferences/types.d.ts.map +1 -0
  170. package/dist/plugins/preferences/types.js +10 -0
  171. package/dist/plugins/preferences/types.js.map +1 -0
  172. package/dist/plugins/users/__tests__/users-plugin.test.d.ts +9 -0
  173. package/dist/plugins/users/__tests__/users-plugin.test.d.ts.map +1 -0
  174. package/dist/plugins/users/__tests__/users-plugin.test.js +546 -0
  175. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -0
  176. package/dist/plugins/users/index.d.ts +12 -0
  177. package/dist/plugins/users/index.d.ts.map +1 -0
  178. package/dist/plugins/users/index.js +13 -0
  179. package/dist/plugins/users/index.js.map +1 -0
  180. package/dist/plugins/users/stores/index.d.ts +7 -0
  181. package/dist/plugins/users/stores/index.d.ts.map +1 -0
  182. package/dist/plugins/users/stores/index.js +7 -0
  183. package/dist/plugins/users/stores/index.js.map +1 -0
  184. package/dist/plugins/users/stores/postgres-store.d.ts +28 -0
  185. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -0
  186. package/dist/plugins/users/stores/postgres-store.js +157 -0
  187. package/dist/plugins/users/stores/postgres-store.js.map +1 -0
  188. package/dist/plugins/users/types.d.ts +225 -0
  189. package/dist/plugins/users/types.d.ts.map +1 -0
  190. package/dist/plugins/users/types.js +12 -0
  191. package/dist/plugins/users/types.js.map +1 -0
  192. package/dist/plugins/users/users-plugin.d.ts +45 -0
  193. package/dist/plugins/users/users-plugin.d.ts.map +1 -0
  194. package/dist/plugins/users/users-plugin.js +359 -0
  195. package/dist/plugins/users/users-plugin.js.map +1 -0
  196. package/dist-ui/assets/index-BY8OxNgO.js +465 -0
  197. package/dist-ui/assets/index-BY8OxNgO.js.map +1 -0
  198. package/dist-ui/index.html +1 -1
  199. package/dist-ui-lib/api/controlPanelApi.d.ts +278 -0
  200. package/dist-ui-lib/components/ControlPanelApp.d.ts +61 -0
  201. package/dist-ui-lib/components/index.d.ts +18 -0
  202. package/dist-ui-lib/config/AppConfig.d.ts +7 -0
  203. package/dist-ui-lib/dashboard/DashboardWidgetRegistry.d.ts +62 -0
  204. package/dist-ui-lib/dashboard/DashboardWidgetRenderer.d.ts +8 -0
  205. package/dist-ui-lib/dashboard/PluginWidgetRenderer.d.ts +19 -0
  206. package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +48 -0
  207. package/dist-ui-lib/dashboard/builtInWidgets.d.ts +25 -0
  208. package/dist-ui-lib/dashboard/index.d.ts +13 -0
  209. package/dist-ui-lib/dashboard/widgets/ServiceHealthWidget.d.ts +12 -0
  210. package/dist-ui-lib/dashboard/widgets/index.d.ts +6 -0
  211. package/dist-ui-lib/index.js +5172 -0
  212. package/dist-ui-lib/index.js.map +1 -0
  213. package/dist-ui-lib/pages/AuthPage.d.ts +1 -0
  214. package/dist-ui-lib/pages/ConfigPage.d.ts +1 -0
  215. package/dist-ui-lib/pages/DashboardPage.d.ts +1 -0
  216. package/dist-ui-lib/pages/DiagnosticsPage.d.ts +1 -0
  217. package/dist-ui-lib/pages/EntitlementsPage.d.ts +17 -0
  218. package/dist-ui-lib/pages/LogsPage.d.ts +1 -0
  219. package/dist-ui-lib/pages/NotFoundPage.d.ts +1 -0
  220. package/dist-ui-lib/pages/PluginPage.d.ts +15 -0
  221. package/dist-ui-lib/pages/PluginsPage.d.ts +1 -0
  222. package/dist-ui-lib/pages/SystemPage.d.ts +1 -0
  223. package/dist-ui-lib/pages/UsersPage.d.ts +22 -0
  224. package/package.json +24 -7
  225. package/src/core/control-panel.ts +145 -61
  226. package/src/core/gateway.ts +863 -403
  227. package/src/core/index.ts +21 -2
  228. package/src/core/plugin-registry.ts +716 -0
  229. package/src/core/types.ts +31 -37
  230. package/src/index.ts +125 -19
  231. package/src/plugins/auth/adapters/auth0-adapter.ts +214 -0
  232. package/src/plugins/auth/adapters/basic-adapter.ts +61 -0
  233. package/src/plugins/auth/adapters/index.ts +10 -0
  234. package/src/plugins/auth/adapters/supabase-adapter.ts +149 -0
  235. package/src/plugins/auth/adapters/supertokens-adapter.ts +326 -0
  236. package/src/plugins/auth/auth-plugin.test.ts +176 -0
  237. package/src/plugins/auth/auth-plugin.ts +303 -0
  238. package/src/plugins/auth/env-config.ts +572 -0
  239. package/src/plugins/auth/index.ts +42 -0
  240. package/src/plugins/auth/supertokens-adapter.test.ts +621 -0
  241. package/src/plugins/auth/types.ts +245 -0
  242. package/src/plugins/bans/bans-plugin.ts +485 -0
  243. package/src/plugins/bans/index.ts +31 -0
  244. package/src/plugins/bans/stores/index.ts +7 -0
  245. package/src/plugins/bans/stores/postgres-store.ts +195 -0
  246. package/src/plugins/bans/types.ts +141 -0
  247. package/src/plugins/cache-plugin.test.ts +108 -32
  248. package/src/plugins/cache-plugin.ts +40 -9
  249. package/src/plugins/config-plugin.ts +23 -12
  250. package/src/plugins/diagnostics-plugin.ts +22 -12
  251. package/src/plugins/entitlements/entitlements-plugin.ts +820 -0
  252. package/src/plugins/entitlements/index.ts +51 -0
  253. package/src/plugins/entitlements/sources/index.ts +9 -0
  254. package/src/plugins/entitlements/sources/postgres-source.ts +253 -0
  255. package/src/plugins/entitlements/types.ts +256 -0
  256. package/src/plugins/frontend-app-plugin.ts +24 -12
  257. package/src/plugins/health-plugin.ts +27 -7
  258. package/src/plugins/index.ts +132 -4
  259. package/src/plugins/logs-plugin.ts +28 -14
  260. package/src/plugins/postgres-plugin.test.ts +52 -29
  261. package/src/plugins/postgres-plugin.ts +11 -9
  262. package/src/plugins/preferences/__tests__/deep-merge.test.ts +242 -0
  263. package/src/plugins/preferences/__tests__/preferences-plugin.test.ts +350 -0
  264. package/src/plugins/preferences/index.ts +30 -0
  265. package/src/plugins/preferences/preferences-plugin.ts +270 -0
  266. package/src/plugins/preferences/stores/index.ts +9 -0
  267. package/src/plugins/preferences/stores/postgres-store.ts +252 -0
  268. package/src/plugins/preferences/types.ts +100 -0
  269. package/src/plugins/users/__tests__/users-plugin.test.ts +690 -0
  270. package/src/plugins/users/index.ts +38 -0
  271. package/src/plugins/users/stores/index.ts +7 -0
  272. package/src/plugins/users/stores/postgres-store.ts +225 -0
  273. package/src/plugins/users/types.ts +247 -0
  274. package/src/plugins/users/users-plugin.ts +418 -0
  275. package/ui/src/App.tsx +188 -31
  276. package/ui/src/api/controlPanelApi.ts +453 -1
  277. package/ui/src/components/ControlPanelApp.tsx +212 -0
  278. package/ui/src/components/index.ts +62 -0
  279. package/ui/src/dashboard/DashboardWidgetRegistry.tsx +129 -0
  280. package/ui/src/dashboard/DashboardWidgetRenderer.tsx +34 -0
  281. package/ui/src/dashboard/PluginWidgetRenderer.tsx +118 -0
  282. package/ui/src/dashboard/WidgetComponentRegistry.tsx +120 -0
  283. package/ui/src/dashboard/builtInWidgets.tsx +35 -0
  284. package/ui/src/dashboard/index.ts +35 -0
  285. package/ui/src/dashboard/widgets/ServiceHealthWidget.tsx +140 -0
  286. package/ui/src/dashboard/widgets/index.ts +7 -0
  287. package/ui/src/pages/AuthPage.tsx +259 -0
  288. package/ui/src/pages/DashboardPage.tsx +28 -149
  289. package/ui/src/pages/EntitlementsPage.tsx +557 -0
  290. package/ui/src/pages/LogsPage.tsx +174 -8
  291. package/ui/src/pages/PluginPage.tsx +148 -0
  292. package/ui/src/pages/PluginsPage.tsx +394 -0
  293. package/ui/src/pages/SystemPage.tsx +445 -0
  294. package/ui/src/pages/UsersPage.tsx +837 -0
  295. package/ui/tsconfig.lib.json +11 -0
  296. package/ui/vite.lib.config.ts +56 -0
  297. package/dist-ui/assets/index-CW1BviRn.js +0 -465
  298. package/dist-ui/assets/index-CW1BviRn.js.map +0 -1
  299. package/ui/src/pages/HealthPage.tsx +0 -204
@@ -0,0 +1,394 @@
1
+ import { useState, useEffect } from 'react';
2
+ import {
3
+ Box,
4
+ Card,
5
+ CardContent,
6
+ Typography,
7
+ CircularProgress,
8
+ Chip,
9
+ Table,
10
+ TableBody,
11
+ TableCell,
12
+ TableContainer,
13
+ TableHead,
14
+ TableRow,
15
+ Collapse,
16
+ IconButton,
17
+ Alert,
18
+ } from '@mui/material';
19
+ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
20
+ import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
21
+ import ExtensionIcon from '@mui/icons-material/Extension';
22
+ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
23
+ import ErrorIcon from '@mui/icons-material/Error';
24
+ import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty';
25
+ import StopCircleIcon from '@mui/icons-material/StopCircle';
26
+ import { api, PluginInfo, PluginContributions } from '../api/controlPanelApi';
27
+
28
+ function getStatusIcon(status: PluginInfo['status']) {
29
+ switch (status) {
30
+ case 'active':
31
+ return <CheckCircleIcon sx={{ color: 'var(--theme-success)', fontSize: 20 }} />;
32
+ case 'error':
33
+ return <ErrorIcon sx={{ color: 'var(--theme-error)', fontSize: 20 }} />;
34
+ case 'starting':
35
+ return <HourglassEmptyIcon sx={{ color: 'var(--theme-warning)', fontSize: 20 }} />;
36
+ case 'stopped':
37
+ return <StopCircleIcon sx={{ color: 'var(--theme-text-secondary)', fontSize: 20 }} />;
38
+ default:
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function getStatusColor(status: PluginInfo['status']): string {
44
+ switch (status) {
45
+ case 'active':
46
+ return 'var(--theme-success)';
47
+ case 'error':
48
+ return 'var(--theme-error)';
49
+ case 'starting':
50
+ return 'var(--theme-warning)';
51
+ case 'stopped':
52
+ return 'var(--theme-text-secondary)';
53
+ default:
54
+ return 'var(--theme-text-secondary)';
55
+ }
56
+ }
57
+
58
+ function ContributionsSummary({ counts }: { counts: PluginInfo['contributionCounts'] }) {
59
+ const parts: string[] = [];
60
+ if (counts.routes > 0) parts.push(`${counts.routes} route${counts.routes > 1 ? 's' : ''}`);
61
+ if (counts.menuItems > 0) parts.push(`${counts.menuItems} menu item${counts.menuItems > 1 ? 's' : ''}`);
62
+ if (counts.pages > 0) parts.push(`${counts.pages} page${counts.pages > 1 ? 's' : ''}`);
63
+ if (counts.widgets > 0) parts.push(`${counts.widgets} widget${counts.widgets > 1 ? 's' : ''}`);
64
+
65
+ if (parts.length === 0) {
66
+ return <Typography sx={{ color: 'var(--theme-text-secondary)', fontSize: '0.875rem' }}>No contributions</Typography>;
67
+ }
68
+
69
+ return (
70
+ <Typography sx={{ color: 'var(--theme-text-secondary)', fontSize: '0.875rem' }}>
71
+ {parts.join(', ')}
72
+ </Typography>
73
+ );
74
+ }
75
+
76
+ interface PluginRowProps {
77
+ plugin: PluginInfo;
78
+ }
79
+
80
+ function PluginRow({ plugin }: PluginRowProps) {
81
+ const [open, setOpen] = useState(false);
82
+ const [contributions, setContributions] = useState<PluginContributions | null>(null);
83
+ const [loading, setLoading] = useState(false);
84
+ const [detailError, setDetailError] = useState<string | null>(null);
85
+
86
+ const handleToggle = async () => {
87
+ if (!open && !contributions && !detailError) {
88
+ setLoading(true);
89
+ setDetailError(null);
90
+ try {
91
+ const detail = await api.getPluginDetail(plugin.id);
92
+ setContributions(detail.contributions);
93
+ } catch (err) {
94
+ console.error('Failed to load plugin details:', err);
95
+ setDetailError(err instanceof Error ? err.message : 'Failed to load details');
96
+ } finally {
97
+ setLoading(false);
98
+ }
99
+ }
100
+ setOpen(!open);
101
+ };
102
+
103
+ const hasContributions =
104
+ plugin.contributionCounts.routes > 0 ||
105
+ plugin.contributionCounts.menuItems > 0 ||
106
+ plugin.contributionCounts.pages > 0 ||
107
+ plugin.contributionCounts.widgets > 0;
108
+
109
+ return (
110
+ <>
111
+ <TableRow
112
+ sx={{
113
+ '& > *': { borderBottom: open ? 'none' : undefined },
114
+ cursor: hasContributions ? 'pointer' : 'default',
115
+ '&:hover': { bgcolor: hasContributions ? 'var(--theme-background)' : undefined },
116
+ }}
117
+ onClick={hasContributions ? handleToggle : undefined}
118
+ >
119
+ <TableCell sx={{ width: 50, borderColor: 'var(--theme-border)' }}>
120
+ {hasContributions && (
121
+ <IconButton size="small" sx={{ color: 'var(--theme-text-secondary)' }}>
122
+ {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
123
+ </IconButton>
124
+ )}
125
+ </TableCell>
126
+ <TableCell sx={{ borderColor: 'var(--theme-border)' }}>
127
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
128
+ {getStatusIcon(plugin.status)}
129
+ <Typography sx={{ color: 'var(--theme-text-primary)', fontWeight: 500 }}>
130
+ {plugin.name}
131
+ </Typography>
132
+ </Box>
133
+ </TableCell>
134
+ <TableCell sx={{ borderColor: 'var(--theme-border)' }}>
135
+ <Typography sx={{ color: 'var(--theme-text-secondary)', fontFamily: 'monospace', fontSize: '0.875rem' }}>
136
+ {plugin.id}
137
+ </Typography>
138
+ </TableCell>
139
+ <TableCell sx={{ borderColor: 'var(--theme-border)' }}>
140
+ {plugin.version ? (
141
+ <Chip
142
+ label={`v${plugin.version}`}
143
+ size="small"
144
+ sx={{ bgcolor: 'var(--theme-background)', color: 'var(--theme-text-primary)' }}
145
+ />
146
+ ) : (
147
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>-</Typography>
148
+ )}
149
+ </TableCell>
150
+ <TableCell sx={{ borderColor: 'var(--theme-border)' }}>
151
+ <Chip
152
+ label={plugin.status}
153
+ size="small"
154
+ sx={{
155
+ bgcolor: getStatusColor(plugin.status) + '20',
156
+ color: getStatusColor(plugin.status),
157
+ textTransform: 'capitalize',
158
+ }}
159
+ />
160
+ </TableCell>
161
+ <TableCell sx={{ borderColor: 'var(--theme-border)' }}>
162
+ <ContributionsSummary counts={plugin.contributionCounts} />
163
+ </TableCell>
164
+ </TableRow>
165
+
166
+ {/* Expanded details row */}
167
+ <TableRow>
168
+ <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6} sx={{ borderColor: 'var(--theme-border)' }}>
169
+ <Collapse in={open} timeout="auto" unmountOnExit>
170
+ <Box sx={{ py: 2, px: 4 }}>
171
+ {loading ? (
172
+ <Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
173
+ <CircularProgress size={24} />
174
+ </Box>
175
+ ) : detailError ? (
176
+ <Alert severity="error" sx={{ mb: 1 }}>
177
+ {detailError}
178
+ </Alert>
179
+ ) : contributions ? (
180
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
181
+ {/* Error message if plugin failed */}
182
+ {plugin.status === 'error' && plugin.error && (
183
+ <Alert severity="error" sx={{ mb: 1 }}>
184
+ {plugin.error}
185
+ </Alert>
186
+ )}
187
+
188
+ {/* Routes */}
189
+ {contributions.routes.length > 0 && (
190
+ <Box>
191
+ <Typography variant="subtitle2" sx={{ color: 'var(--theme-text-primary)', mb: 1 }}>
192
+ Routes ({contributions.routes.length})
193
+ </Typography>
194
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
195
+ {contributions.routes.map((route, i) => (
196
+ <Chip
197
+ key={i}
198
+ label={`${route.method.toUpperCase()} ${route.path}`}
199
+ size="small"
200
+ sx={{
201
+ bgcolor: 'var(--theme-background)',
202
+ color: 'var(--theme-text-primary)',
203
+ fontFamily: 'monospace',
204
+ fontSize: '0.75rem',
205
+ }}
206
+ />
207
+ ))}
208
+ </Box>
209
+ </Box>
210
+ )}
211
+
212
+ {/* Menu Items */}
213
+ {contributions.menuItems.length > 0 && (
214
+ <Box>
215
+ <Typography variant="subtitle2" sx={{ color: 'var(--theme-text-primary)', mb: 1 }}>
216
+ Menu Items ({contributions.menuItems.length})
217
+ </Typography>
218
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
219
+ {contributions.menuItems.map((item) => (
220
+ <Chip
221
+ key={item.id}
222
+ label={`${item.label} (${item.route})`}
223
+ size="small"
224
+ sx={{ bgcolor: 'var(--theme-background)', color: 'var(--theme-text-primary)' }}
225
+ />
226
+ ))}
227
+ </Box>
228
+ </Box>
229
+ )}
230
+
231
+ {/* Pages */}
232
+ {contributions.pages.length > 0 && (
233
+ <Box>
234
+ <Typography variant="subtitle2" sx={{ color: 'var(--theme-text-primary)', mb: 1 }}>
235
+ Pages ({contributions.pages.length})
236
+ </Typography>
237
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
238
+ {contributions.pages.map((page) => (
239
+ <Chip
240
+ key={page.id}
241
+ label={`${page.title || page.id} (${page.route})`}
242
+ size="small"
243
+ sx={{ bgcolor: 'var(--theme-background)', color: 'var(--theme-text-primary)' }}
244
+ />
245
+ ))}
246
+ </Box>
247
+ </Box>
248
+ )}
249
+
250
+ {/* Widgets */}
251
+ {contributions.widgets.length > 0 && (
252
+ <Box>
253
+ <Typography variant="subtitle2" sx={{ color: 'var(--theme-text-primary)', mb: 1 }}>
254
+ Widgets ({contributions.widgets.length})
255
+ </Typography>
256
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
257
+ {contributions.widgets.map((widget) => (
258
+ <Chip
259
+ key={widget.id}
260
+ label={widget.title}
261
+ size="small"
262
+ sx={{ bgcolor: 'var(--theme-background)', color: 'var(--theme-text-primary)' }}
263
+ />
264
+ ))}
265
+ </Box>
266
+ </Box>
267
+ )}
268
+ </Box>
269
+ ) : null}
270
+ </Box>
271
+ </Collapse>
272
+ </TableCell>
273
+ </TableRow>
274
+ </>
275
+ );
276
+ }
277
+
278
+ export function PluginsPage() {
279
+ const [plugins, setPlugins] = useState<PluginInfo[]>([]);
280
+ const [loading, setLoading] = useState(true);
281
+ const [error, setError] = useState<string | null>(null);
282
+
283
+ useEffect(() => {
284
+ const fetchPlugins = async () => {
285
+ try {
286
+ const data = await api.getPlugins();
287
+ setPlugins(data.plugins);
288
+ setError(null);
289
+ } catch (err) {
290
+ setError(err instanceof Error ? err.message : 'Failed to fetch plugins');
291
+ } finally {
292
+ setLoading(false);
293
+ }
294
+ };
295
+
296
+ fetchPlugins();
297
+ }, []);
298
+
299
+ if (loading) {
300
+ return (
301
+ <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
302
+ <CircularProgress />
303
+ </Box>
304
+ );
305
+ }
306
+
307
+ if (error) {
308
+ return (
309
+ <Card sx={{ bgcolor: 'var(--theme-surface)', border: '1px solid var(--theme-error)' }}>
310
+ <CardContent>
311
+ <Typography color="error">{error}</Typography>
312
+ </CardContent>
313
+ </Card>
314
+ );
315
+ }
316
+
317
+ // Count plugins by status
318
+ const activeCount = plugins.filter((p) => p.status === 'active').length;
319
+ const errorCount = plugins.filter((p) => p.status === 'error').length;
320
+
321
+ return (
322
+ <Box>
323
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
324
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
325
+ <Typography variant="h4" sx={{ color: 'var(--theme-text-primary)' }}>
326
+ Plugins
327
+ </Typography>
328
+ <Chip
329
+ icon={<ExtensionIcon sx={{ fontSize: 16 }} />}
330
+ label={`${activeCount}/${plugins.length} active`}
331
+ size="small"
332
+ sx={{
333
+ bgcolor: activeCount === plugins.length ? 'var(--theme-success)20' : 'var(--theme-warning)20',
334
+ color: activeCount === plugins.length ? 'var(--theme-success)' : 'var(--theme-warning)',
335
+ }}
336
+ />
337
+ {errorCount > 0 && (
338
+ <Chip
339
+ icon={<ErrorIcon sx={{ fontSize: 16 }} />}
340
+ label={`${errorCount} error${errorCount > 1 ? 's' : ''}`}
341
+ size="small"
342
+ sx={{ bgcolor: 'var(--theme-error)20', color: 'var(--theme-error)' }}
343
+ />
344
+ )}
345
+ </Box>
346
+ </Box>
347
+ <Typography variant="body2" sx={{ mb: 4, color: 'var(--theme-text-secondary)' }}>
348
+ View registered plugins and their contributions to the control panel
349
+ </Typography>
350
+
351
+ {plugins.length === 0 ? (
352
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
353
+ <CardContent>
354
+ <Typography sx={{ color: 'var(--theme-text-secondary)', textAlign: 'center', py: 4 }}>
355
+ No plugins registered
356
+ </Typography>
357
+ </CardContent>
358
+ </Card>
359
+ ) : (
360
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
361
+ <TableContainer>
362
+ <Table>
363
+ <TableHead>
364
+ <TableRow>
365
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)', width: 50 }} />
366
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
367
+ Name
368
+ </TableCell>
369
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
370
+ ID
371
+ </TableCell>
372
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
373
+ Version
374
+ </TableCell>
375
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
376
+ Status
377
+ </TableCell>
378
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
379
+ Contributions
380
+ </TableCell>
381
+ </TableRow>
382
+ </TableHead>
383
+ <TableBody>
384
+ {plugins.map((plugin) => (
385
+ <PluginRow key={plugin.id} plugin={plugin} />
386
+ ))}
387
+ </TableBody>
388
+ </Table>
389
+ </TableContainer>
390
+ </Card>
391
+ )}
392
+ </Box>
393
+ );
394
+ }