@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
@@ -20,12 +20,13 @@ export interface HealthResponse {
20
20
 
21
21
  export interface InfoResponse {
22
22
  product: string;
23
+ logoName: string;
24
+ logoIconUrl?: string;
23
25
  version: string;
24
26
  uptime: number;
25
27
  links: Array<{ label: string; url: string; external?: boolean }>;
26
28
  branding?: {
27
29
  primaryColor?: string;
28
- logo?: string;
29
30
  };
30
31
  }
31
32
 
@@ -33,6 +34,8 @@ export interface DiagnosticsResponse {
33
34
  timestamp: string;
34
35
  product: string;
35
36
  version?: string;
37
+ /** @qwickapps/server framework version */
38
+ frameworkVersion?: string;
36
39
  uptime: number;
37
40
  health: Record<string, HealthCheck>;
38
41
  system: {
@@ -75,6 +78,192 @@ export interface LogSource {
75
78
  available: boolean;
76
79
  }
77
80
 
81
+ // ==================
82
+ // Users API Types
83
+ // ==================
84
+ export interface User {
85
+ id: string;
86
+ email: string;
87
+ name?: string;
88
+ created_at?: string;
89
+ updated_at?: string;
90
+ last_login?: string;
91
+ metadata?: Record<string, unknown>;
92
+ }
93
+
94
+ export interface UsersResponse {
95
+ users: User[];
96
+ total: number;
97
+ page: number;
98
+ limit: number;
99
+ }
100
+
101
+ // ==================
102
+ // Bans API Types
103
+ // ==================
104
+ export interface Ban {
105
+ id: string;
106
+ user_id?: string;
107
+ email: string;
108
+ reason: string;
109
+ banned_at: string;
110
+ banned_by: string;
111
+ expires_at?: string;
112
+ }
113
+
114
+ export interface BansResponse {
115
+ bans: Ban[];
116
+ total: number;
117
+ }
118
+
119
+ // ==================
120
+ // Entitlements API Types
121
+ // ==================
122
+ export interface EntitlementDefinition {
123
+ id: string;
124
+ name: string;
125
+ category?: string;
126
+ description?: string;
127
+ }
128
+
129
+ export interface EntitlementResult {
130
+ identifier: string;
131
+ entitlements: string[];
132
+ source: string;
133
+ cached?: boolean;
134
+ cachedAt?: string;
135
+ expiresAt?: string;
136
+ }
137
+
138
+ // ==================
139
+ // Entitlements Status
140
+ // ==================
141
+ export interface EntitlementSourceInfo {
142
+ name: string;
143
+ description?: string;
144
+ readonly: boolean;
145
+ primary: boolean;
146
+ }
147
+
148
+ export interface EntitlementsStatus {
149
+ readonly: boolean;
150
+ writeEnabled: boolean;
151
+ cacheEnabled: boolean;
152
+ cacheTtl: number;
153
+ sources: EntitlementSourceInfo[];
154
+ }
155
+
156
+ // ==================
157
+ // Plugin Feature Detection
158
+ // ==================
159
+ export interface PluginFeatures {
160
+ users: boolean;
161
+ bans: boolean;
162
+ entitlements: boolean;
163
+ entitlementsReadonly?: boolean;
164
+ }
165
+
166
+ // ==================
167
+ // UI Contributions Types
168
+ // ==================
169
+
170
+ export interface MenuContribution {
171
+ id: string;
172
+ label: string;
173
+ icon?: string;
174
+ route: string;
175
+ order?: number;
176
+ pluginId: string;
177
+ parent?: string;
178
+ }
179
+
180
+ export interface PageContribution {
181
+ id: string;
182
+ route: string;
183
+ title: string;
184
+ pluginId: string;
185
+ }
186
+
187
+ export interface WidgetContribution {
188
+ id: string;
189
+ title: string;
190
+ /** Component name to render (matched by frontend widget registry) */
191
+ component: string;
192
+ /** Priority for ordering (lower = first, default: 100) */
193
+ priority?: number;
194
+ /** Whether this widget is shown by default */
195
+ showByDefault?: boolean;
196
+ pluginId: string;
197
+ }
198
+
199
+ export interface UiContributionsResponse {
200
+ menuItems: MenuContribution[];
201
+ pages: PageContribution[];
202
+ widgets: WidgetContribution[];
203
+ plugins: Array<{ id: string; name: string; version?: string; status: string }>;
204
+ }
205
+
206
+ // ==================
207
+ // Plugin Detail Types
208
+ // ==================
209
+
210
+ export interface ConfigContribution {
211
+ id: string;
212
+ component: string;
213
+ title?: string;
214
+ pluginId: string;
215
+ }
216
+
217
+ export interface PluginContributions {
218
+ routes: Array<{ method: string; path: string }>;
219
+ menuItems: MenuContribution[];
220
+ pages: PageContribution[];
221
+ widgets: WidgetContribution[];
222
+ config?: ConfigContribution;
223
+ }
224
+
225
+ export interface PluginInfo {
226
+ id: string;
227
+ name: string;
228
+ version?: string;
229
+ status: 'starting' | 'active' | 'stopped' | 'error';
230
+ error?: string;
231
+ contributionCounts: {
232
+ routes: number;
233
+ menuItems: number;
234
+ pages: number;
235
+ widgets: number;
236
+ hasConfig: boolean;
237
+ };
238
+ }
239
+
240
+ export interface PluginsResponse {
241
+ plugins: PluginInfo[];
242
+ }
243
+
244
+ export interface PluginDetailResponse {
245
+ id: string;
246
+ name: string;
247
+ version?: string;
248
+ status: 'starting' | 'active' | 'stopped' | 'error';
249
+ error?: string;
250
+ contributions: PluginContributions;
251
+ }
252
+
253
+ // ==================
254
+ // Auth Config Types
255
+ // ==================
256
+
257
+ export type AuthPluginState = 'disabled' | 'enabled' | 'error';
258
+
259
+ export interface AuthConfigStatus {
260
+ state: AuthPluginState;
261
+ adapter: string | null;
262
+ error?: string;
263
+ missingVars?: string[];
264
+ config?: Record<string, string>;
265
+ }
266
+
78
267
  class ControlPanelApi {
79
268
  private baseUrl: string;
80
269
 
@@ -82,6 +271,207 @@ class ControlPanelApi {
82
271
  this.baseUrl = baseUrl;
83
272
  }
84
273
 
274
+ /**
275
+ * Set the base URL for API requests.
276
+ * Call this when the control panel is mounted at a custom path.
277
+ */
278
+ setBaseUrl(baseUrl: string): void {
279
+ this.baseUrl = baseUrl;
280
+ }
281
+
282
+ // ==================
283
+ // Plugin Feature Detection
284
+ // ==================
285
+
286
+ /**
287
+ * Detect which user management plugins are available by probing their endpoints
288
+ */
289
+ async detectFeatures(): Promise<PluginFeatures> {
290
+ const [users, bans, entitlements] = await Promise.all([
291
+ this.checkEndpoint('/api/users'),
292
+ this.checkEndpoint('/api/bans'),
293
+ this.checkEndpoint('/api/entitlements/available'),
294
+ ]);
295
+
296
+ // If entitlements is available, get readonly status
297
+ let entitlementsReadonly = true;
298
+ if (entitlements) {
299
+ try {
300
+ const status = await this.getEntitlementsStatus();
301
+ entitlementsReadonly = status.readonly;
302
+ } catch {
303
+ // Default to readonly if we can't get status
304
+ }
305
+ }
306
+
307
+ return { users, bans, entitlements, entitlementsReadonly };
308
+ }
309
+
310
+ private async checkEndpoint(path: string): Promise<boolean> {
311
+ try {
312
+ const response = await fetch(`${this.baseUrl}${path}`, { method: 'HEAD' });
313
+ // 200, 401, 403 mean the endpoint exists (might need auth)
314
+ // 404 means it doesn't exist
315
+ return response.status !== 404;
316
+ } catch {
317
+ return false;
318
+ }
319
+ }
320
+
321
+ // ==================
322
+ // Users API
323
+ // ==================
324
+
325
+ async getUsers(options: {
326
+ limit?: number;
327
+ page?: number;
328
+ search?: string;
329
+ } = {}): Promise<UsersResponse> {
330
+ const params = new URLSearchParams();
331
+ if (options.limit) params.set('limit', options.limit.toString());
332
+ if (options.page) params.set('page', options.page.toString());
333
+ if (options.search) params.set('search', options.search);
334
+
335
+ const response = await fetch(`${this.baseUrl}/api/users?${params}`);
336
+ if (!response.ok) {
337
+ throw new Error(`Users request failed: ${response.statusText}`);
338
+ }
339
+ return response.json();
340
+ }
341
+
342
+ async getUserById(id: string): Promise<User> {
343
+ const response = await fetch(`${this.baseUrl}/api/users/${id}`);
344
+ if (!response.ok) {
345
+ throw new Error(`User request failed: ${response.statusText}`);
346
+ }
347
+ return response.json();
348
+ }
349
+
350
+ // ==================
351
+ // Bans API
352
+ // ==================
353
+
354
+ async getBans(): Promise<BansResponse> {
355
+ const response = await fetch(`${this.baseUrl}/api/bans`);
356
+ if (!response.ok) {
357
+ throw new Error(`Bans request failed: ${response.statusText}`);
358
+ }
359
+ return response.json();
360
+ }
361
+
362
+ async banUser(email: string, reason: string, expiresAt?: string): Promise<void> {
363
+ const response = await fetch(`${this.baseUrl}/api/bans`, {
364
+ method: 'POST',
365
+ headers: { 'Content-Type': 'application/json' },
366
+ body: JSON.stringify({ email, reason, expiresAt }),
367
+ });
368
+ if (!response.ok) {
369
+ const error = await response.json().catch(() => ({}));
370
+ throw new Error(error.error || `Ban request failed: ${response.statusText}`);
371
+ }
372
+ }
373
+
374
+ async unbanUser(email: string): Promise<void> {
375
+ const response = await fetch(`${this.baseUrl}/api/bans/${encodeURIComponent(email)}`, {
376
+ method: 'DELETE',
377
+ });
378
+ if (!response.ok) {
379
+ throw new Error(`Unban request failed: ${response.statusText}`);
380
+ }
381
+ }
382
+
383
+ async checkBan(email: string): Promise<{ banned: boolean; ban?: Ban }> {
384
+ const response = await fetch(`${this.baseUrl}/api/bans/check/${encodeURIComponent(email)}`);
385
+ if (!response.ok) {
386
+ throw new Error(`Ban check failed: ${response.statusText}`);
387
+ }
388
+ return response.json();
389
+ }
390
+
391
+ // ==================
392
+ // Entitlements API
393
+ // ==================
394
+
395
+ async getEntitlements(email: string): Promise<EntitlementResult> {
396
+ const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}`);
397
+ if (!response.ok) {
398
+ throw new Error(`Entitlements request failed: ${response.statusText}`);
399
+ }
400
+ return response.json();
401
+ }
402
+
403
+ async refreshEntitlements(email: string): Promise<EntitlementResult> {
404
+ const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/refresh`, {
405
+ method: 'POST',
406
+ });
407
+ if (!response.ok) {
408
+ throw new Error(`Entitlements refresh failed: ${response.statusText}`);
409
+ }
410
+ return response.json();
411
+ }
412
+
413
+ async checkEntitlement(email: string, entitlement: string): Promise<{ has: boolean }> {
414
+ const response = await fetch(
415
+ `${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/check/${encodeURIComponent(entitlement)}`
416
+ );
417
+ if (!response.ok) {
418
+ throw new Error(`Entitlement check failed: ${response.statusText}`);
419
+ }
420
+ return response.json();
421
+ }
422
+
423
+ async getAvailableEntitlements(): Promise<EntitlementDefinition[]> {
424
+ const response = await fetch(`${this.baseUrl}/api/entitlements/available`);
425
+ if (!response.ok) {
426
+ throw new Error(`Available entitlements request failed: ${response.statusText}`);
427
+ }
428
+ const data = await response.json();
429
+ return data.entitlements;
430
+ }
431
+
432
+ async grantEntitlement(email: string, entitlement: string): Promise<void> {
433
+ const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}`, {
434
+ method: 'POST',
435
+ headers: { 'Content-Type': 'application/json' },
436
+ body: JSON.stringify({ entitlement }),
437
+ });
438
+ if (!response.ok) {
439
+ const error = await response.json().catch(() => ({}));
440
+ throw new Error(error.error || `Grant entitlement failed: ${response.statusText}`);
441
+ }
442
+ }
443
+
444
+ async revokeEntitlement(email: string, entitlement: string): Promise<void> {
445
+ const response = await fetch(
446
+ `${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/${encodeURIComponent(entitlement)}`,
447
+ { method: 'DELETE' }
448
+ );
449
+ if (!response.ok) {
450
+ throw new Error(`Revoke entitlement failed: ${response.statusText}`);
451
+ }
452
+ }
453
+
454
+ async invalidateEntitlementCache(email: string): Promise<void> {
455
+ const response = await fetch(`${this.baseUrl}/api/entitlements/cache/${encodeURIComponent(email)}`, {
456
+ method: 'DELETE',
457
+ });
458
+ if (!response.ok) {
459
+ throw new Error(`Cache invalidation failed: ${response.statusText}`);
460
+ }
461
+ }
462
+
463
+ async getEntitlementsStatus(): Promise<EntitlementsStatus> {
464
+ const response = await fetch(`${this.baseUrl}/api/entitlements/status`);
465
+ if (!response.ok) {
466
+ throw new Error(`Entitlements status request failed: ${response.statusText}`);
467
+ }
468
+ return response.json();
469
+ }
470
+
471
+ // ==================
472
+ // Health API
473
+ // ==================
474
+
85
475
  async getHealth(): Promise<HealthResponse> {
86
476
  const response = await fetch(`${this.baseUrl}/api/health`);
87
477
  if (!response.ok) {
@@ -143,6 +533,68 @@ class ControlPanelApi {
143
533
  const data = await response.json();
144
534
  return data.sources;
145
535
  }
536
+
537
+ // ==================
538
+ // Plugins API
539
+ // ==================
540
+
541
+ async getPlugins(): Promise<PluginsResponse> {
542
+ const response = await fetch(`${this.baseUrl}/api/plugins`);
543
+ if (!response.ok) {
544
+ throw new Error(`Plugins request failed: ${response.statusText}`);
545
+ }
546
+ return response.json();
547
+ }
548
+
549
+ async getPluginDetail(id: string): Promise<PluginDetailResponse> {
550
+ const response = await fetch(`${this.baseUrl}/api/plugins/${encodeURIComponent(id)}`);
551
+ if (!response.ok) {
552
+ if (response.status === 404) {
553
+ throw new Error(`Plugin not found: ${id}`);
554
+ }
555
+ throw new Error(`Plugin detail request failed: ${response.statusText}`);
556
+ }
557
+ return response.json();
558
+ }
559
+
560
+ // ==================
561
+ // UI Contributions API
562
+ // ==================
563
+
564
+ async getUiContributions(): Promise<UiContributionsResponse> {
565
+ const response = await fetch(`${this.baseUrl}/api/ui-contributions`);
566
+ if (!response.ok) {
567
+ throw new Error(`UI contributions request failed: ${response.statusText}`);
568
+ }
569
+ return response.json();
570
+ }
571
+
572
+ // ==================
573
+ // Auth Config API
574
+ // ==================
575
+
576
+ async getAuthConfigStatus(): Promise<AuthConfigStatus> {
577
+ const response = await fetch(`${this.baseUrl}/api/auth/config/status`);
578
+ if (!response.ok) {
579
+ // Return disabled state if endpoint not available
580
+ if (response.status === 404) {
581
+ return { state: 'disabled', adapter: null };
582
+ }
583
+ throw new Error(`Auth config status request failed: ${response.statusText}`);
584
+ }
585
+ return response.json();
586
+ }
587
+
588
+ async getAuthConfig(): Promise<AuthConfigStatus> {
589
+ const response = await fetch(`${this.baseUrl}/api/auth/config`);
590
+ if (!response.ok) {
591
+ if (response.status === 404) {
592
+ return { state: 'disabled', adapter: null };
593
+ }
594
+ throw new Error(`Auth config request failed: ${response.statusText}`);
595
+ }
596
+ return response.json();
597
+ }
146
598
  }
147
599
 
148
600
  export const api = new ControlPanelApi();
@@ -0,0 +1,212 @@
1
+ /**
2
+ * ControlPanelApp Component
3
+ *
4
+ * A wrapper around QwickApp that provides control panel functionality.
5
+ * Injects base control panel routes (Dashboard, Health, Logs, System)
6
+ * and allows consumers to add custom routes.
7
+ *
8
+ * Usage:
9
+ * ```tsx
10
+ * import { ControlPanelApp } from '@qwickapps/server/ui';
11
+ *
12
+ * function App() {
13
+ * return (
14
+ * <ControlPanelApp
15
+ * productName="My Service"
16
+ * logo={<MyLogo />}
17
+ * customDashboard={<MyDashboard />}
18
+ * >
19
+ * <Route path="/users" element={<UsersPage />} />
20
+ * <Route path="/settings" element={<SettingsPage />} />
21
+ * </ControlPanelApp>
22
+ * );
23
+ * }
24
+ * ```
25
+ *
26
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
27
+ */
28
+
29
+ import { ReactNode, useState, useEffect } from 'react';
30
+ import { Routes, Route } from 'react-router-dom';
31
+ import { Box, Link } from '@mui/material';
32
+ import { QwickApp, ProductLogo, Text, type MenuItem } from '@qwickapps/react-framework';
33
+ import { defaultConfig } from '../config/AppConfig';
34
+
35
+ // Base pages
36
+ import { DashboardPage } from '../pages/DashboardPage';
37
+ import { LogsPage } from '../pages/LogsPage';
38
+ import { SystemPage } from '../pages/SystemPage';
39
+ import { AuthPage } from '../pages/AuthPage';
40
+ import { NotFoundPage } from '../pages/NotFoundPage';
41
+
42
+ // Dashboard widget system
43
+ import {
44
+ DashboardWidgetProvider,
45
+ WidgetComponentRegistryProvider,
46
+ getBuiltInWidgetComponents,
47
+ type DashboardWidget,
48
+ type WidgetComponent,
49
+ } from '../dashboard';
50
+
51
+ // API
52
+ import { api } from '../api/controlPanelApi';
53
+
54
+ export interface ControlPanelAppProps {
55
+ /** Product name displayed in the header */
56
+ productName?: string;
57
+
58
+ /** Custom logo component */
59
+ logo?: ReactNode;
60
+
61
+ /** Custom footer content (replaces default) */
62
+ footerContent?: ReactNode;
63
+
64
+ /** Initial dashboard widgets to register (legacy context-based system) */
65
+ dashboardWidgets?: DashboardWidget[];
66
+
67
+ /**
68
+ * Widget components to register for the plugin-based widget system.
69
+ * These map component names (from server WidgetContribution) to React components.
70
+ * Built-in widgets (ServiceHealthWidget, etc.) are registered automatically.
71
+ */
72
+ widgetComponents?: WidgetComponent[];
73
+
74
+ /** Additional navigation items to add to the base control panel nav */
75
+ navigationItems?: MenuItem[];
76
+
77
+ /** Whether to show the base control panel navigation (Dashboard, Health, etc.) */
78
+ showBaseNavigation?: boolean;
79
+
80
+ /** Base navigation item IDs to hide (e.g., ['health'] to hide the Health page) */
81
+ hideBaseNavItems?: string[];
82
+
83
+ /** Whether to show theme switcher in settings */
84
+ showThemeSwitcher?: boolean;
85
+
86
+ /** Whether to show palette switcher in settings */
87
+ showPaletteSwitcher?: boolean;
88
+
89
+ /** Base path for the control panel (e.g., '/cpanel') */
90
+ basePath?: string;
91
+
92
+ /** Custom routes to add (as Route elements) */
93
+ children?: ReactNode;
94
+ }
95
+
96
+ /**
97
+ * Default footer with QwickApps Server branding
98
+ */
99
+ function DefaultFooter({ version }: { version: string }) {
100
+ return (
101
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, py: 2 }}>
102
+ <Text variant="caption" customColor="var(--theme-text-secondary)">
103
+ Built with{' '}
104
+ <Link
105
+ href="https://qwickapps.com/products/qwickapps-server"
106
+ target="_blank"
107
+ rel="noopener noreferrer"
108
+ sx={{ color: 'primary.main' }}
109
+ >
110
+ QwickApps Server
111
+ </Link>
112
+ {version && ` v${version}`}
113
+ </Text>
114
+ </Box>
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Base navigation items for control panel
120
+ * Routes are relative to BrowserRouter's basename (handled by NavigationProvider in QwickApp)
121
+ */
122
+ function getBaseNavigationItems(): MenuItem[] {
123
+ return [
124
+ { id: 'dashboard', label: 'Dashboard', route: '/', icon: 'dashboard' },
125
+ { id: 'logs', label: 'Logs', route: '/logs', icon: 'article' },
126
+ { id: 'auth', label: 'Auth', route: '/auth', icon: 'lock' },
127
+ { id: 'system', label: 'System', route: '/system', icon: 'settings' },
128
+ ];
129
+ }
130
+
131
+ export function ControlPanelApp({
132
+ productName = 'Control Panel',
133
+ logo,
134
+ footerContent,
135
+ dashboardWidgets = [],
136
+ widgetComponents = [],
137
+ navigationItems = [],
138
+ showBaseNavigation = true,
139
+ hideBaseNavItems = [],
140
+ showThemeSwitcher = true,
141
+ showPaletteSwitcher = true,
142
+ basePath = '',
143
+ children,
144
+ }: ControlPanelAppProps) {
145
+ const [version, setVersion] = useState<string>('');
146
+
147
+ // Combine built-in widget components with custom ones
148
+ const allWidgetComponents = [...getBuiltInWidgetComponents(), ...widgetComponents];
149
+
150
+ // Configure API base URL based on basePath - do this synchronously before any renders
151
+ // If basePath is '/cpanel', API is at '/cpanel/api'
152
+ // If basePath is '' or '/', API is at '/api'
153
+ const apiBasePath = basePath && basePath !== '/' ? basePath : '';
154
+ api.setBaseUrl(apiBasePath);
155
+
156
+ // Fetch version from API
157
+ useEffect(() => {
158
+ api.getInfo()
159
+ .then((info) => setVersion(info.version || ''))
160
+ .catch(() => {});
161
+ }, [apiBasePath]); // Re-fetch when apiBasePath changes
162
+
163
+ // Build navigation: base items (filtered by hideBaseNavItems) + custom items
164
+ // Navigation routes are relative to BrowserRouter's basename
165
+ const filteredBaseItems = showBaseNavigation
166
+ ? getBaseNavigationItems().filter(item => !hideBaseNavItems.includes(item.id))
167
+ : [];
168
+ const allNavigationItems: MenuItem[] = [
169
+ ...filteredBaseItems,
170
+ ...navigationItems,
171
+ ];
172
+
173
+ // Default logo if not provided
174
+ const effectiveLogo = logo || <ProductLogo name={productName} />;
175
+
176
+ // Default footer if not provided
177
+ const effectiveFooter = footerContent || <DefaultFooter version={version} />;
178
+
179
+ return (
180
+ <WidgetComponentRegistryProvider initialComponents={allWidgetComponents}>
181
+ <DashboardWidgetProvider initialWidgets={dashboardWidgets}>
182
+ <QwickApp
183
+ config={defaultConfig}
184
+ logo={effectiveLogo}
185
+ footerContent={effectiveFooter}
186
+ enableScaffolding={true}
187
+ navigationItems={allNavigationItems}
188
+ showThemeSwitcher={showThemeSwitcher}
189
+ showPaletteSwitcher={showPaletteSwitcher}
190
+ >
191
+ <Routes>
192
+ {/* Base control panel routes (filtered by hideBaseNavItems) */}
193
+ {showBaseNavigation && (
194
+ <>
195
+ {!hideBaseNavItems.includes('dashboard') && <Route path="/" element={<DashboardPage />} />}
196
+ {!hideBaseNavItems.includes('logs') && <Route path="/logs" element={<LogsPage />} />}
197
+ {!hideBaseNavItems.includes('auth') && <Route path="/auth" element={<AuthPage />} />}
198
+ {!hideBaseNavItems.includes('system') && <Route path="/system" element={<SystemPage />} />}
199
+ </>
200
+ )}
201
+
202
+ {/* Custom routes from consumer */}
203
+ {children}
204
+
205
+ {/* Catch-all for 404 */}
206
+ <Route path="*" element={<NotFoundPage />} />
207
+ </Routes>
208
+ </QwickApp>
209
+ </DashboardWidgetProvider>
210
+ </WidgetComponentRegistryProvider>
211
+ );
212
+ }