@qwickapps/server 1.3.1 → 1.4.0

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 (149) hide show
  1. package/README.md +157 -0
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +114 -0
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/types.d.ts +19 -0
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
  12. package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
  13. package/dist/plugins/auth/adapter-wrapper.js +166 -0
  14. package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
  15. package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
  16. package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
  17. package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
  18. package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
  19. package/dist/plugins/auth/config-store.d.ts +11 -0
  20. package/dist/plugins/auth/config-store.d.ts.map +1 -0
  21. package/dist/plugins/auth/config-store.js +232 -0
  22. package/dist/plugins/auth/config-store.js.map +1 -0
  23. package/dist/plugins/auth/config-store.test.d.ts +7 -0
  24. package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
  25. package/dist/plugins/auth/config-store.test.js +299 -0
  26. package/dist/plugins/auth/config-store.test.js.map +1 -0
  27. package/dist/plugins/auth/env-config.d.ts +51 -1
  28. package/dist/plugins/auth/env-config.d.ts.map +1 -1
  29. package/dist/plugins/auth/env-config.js +640 -7
  30. package/dist/plugins/auth/env-config.js.map +1 -1
  31. package/dist/plugins/auth/index.d.ts +6 -2
  32. package/dist/plugins/auth/index.d.ts.map +1 -1
  33. package/dist/plugins/auth/index.js +5 -1
  34. package/dist/plugins/auth/index.js.map +1 -1
  35. package/dist/plugins/auth/types.d.ts +106 -0
  36. package/dist/plugins/auth/types.d.ts.map +1 -1
  37. package/dist/plugins/index.d.ts +4 -2
  38. package/dist/plugins/index.d.ts.map +1 -1
  39. package/dist/plugins/index.js +3 -1
  40. package/dist/plugins/index.js.map +1 -1
  41. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
  42. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
  43. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
  44. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
  45. package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
  46. package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
  47. package/dist/plugins/rate-limit/cleanup.js +72 -0
  48. package/dist/plugins/rate-limit/cleanup.js.map +1 -0
  49. package/dist/plugins/rate-limit/env-config.d.ts +91 -0
  50. package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
  51. package/dist/plugins/rate-limit/env-config.js +318 -0
  52. package/dist/plugins/rate-limit/env-config.js.map +1 -0
  53. package/dist/plugins/rate-limit/index.d.ts +76 -0
  54. package/dist/plugins/rate-limit/index.d.ts.map +1 -0
  55. package/dist/plugins/rate-limit/index.js +79 -0
  56. package/dist/plugins/rate-limit/index.js.map +1 -0
  57. package/dist/plugins/rate-limit/middleware.d.ts +40 -0
  58. package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
  59. package/dist/plugins/rate-limit/middleware.js +169 -0
  60. package/dist/plugins/rate-limit/middleware.js.map +1 -0
  61. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
  62. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
  63. package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
  64. package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
  65. package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
  66. package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
  67. package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
  68. package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
  69. package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
  70. package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
  71. package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
  72. package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
  73. package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
  74. package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
  75. package/dist/plugins/rate-limit/stores/index.js +8 -0
  76. package/dist/plugins/rate-limit/stores/index.js.map +1 -0
  77. package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
  78. package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
  79. package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
  80. package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
  81. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
  82. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
  83. package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
  84. package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
  85. package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
  86. package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
  87. package/dist/plugins/rate-limit/strategies/index.js +27 -0
  88. package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
  89. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
  90. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
  91. package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
  92. package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
  93. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
  94. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
  95. package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
  96. package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
  97. package/dist/plugins/rate-limit/types.d.ts +265 -0
  98. package/dist/plugins/rate-limit/types.d.ts.map +1 -0
  99. package/dist/plugins/rate-limit/types.js +9 -0
  100. package/dist/plugins/rate-limit/types.js.map +1 -0
  101. package/dist-ui/assets/index-D7DoZ9rL.js +478 -0
  102. package/dist-ui/assets/index-D7DoZ9rL.js.map +1 -0
  103. package/dist-ui/index.html +1 -1
  104. package/dist-ui-lib/api/controlPanelApi.d.ts +141 -0
  105. package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
  106. package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
  107. package/dist-ui-lib/dashboard/widgets/index.d.ts +2 -0
  108. package/dist-ui-lib/index.js +3332 -2343
  109. package/dist-ui-lib/index.js.map +1 -1
  110. package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
  111. package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
  112. package/package.json +1 -1
  113. package/src/core/control-panel.ts +128 -0
  114. package/src/core/types.ts +17 -0
  115. package/src/index.ts +38 -0
  116. package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
  117. package/src/plugins/auth/adapter-wrapper.ts +205 -0
  118. package/src/plugins/auth/config-store.test.ts +417 -0
  119. package/src/plugins/auth/config-store.ts +305 -0
  120. package/src/plugins/auth/env-config.ts +714 -7
  121. package/src/plugins/auth/index.ts +22 -1
  122. package/src/plugins/auth/types.ts +138 -0
  123. package/src/plugins/index.ts +49 -0
  124. package/src/plugins/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
  125. package/src/plugins/rate-limit/cleanup.ts +117 -0
  126. package/src/plugins/rate-limit/env-config.ts +400 -0
  127. package/src/plugins/rate-limit/index.ts +128 -0
  128. package/src/plugins/rate-limit/middleware.ts +212 -0
  129. package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
  130. package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
  131. package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
  132. package/src/plugins/rate-limit/stores/index.ts +8 -0
  133. package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
  134. package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
  135. package/src/plugins/rate-limit/strategies/index.ts +30 -0
  136. package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
  137. package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
  138. package/src/plugins/rate-limit/types.ts +338 -0
  139. package/ui/src/App.tsx +32 -14
  140. package/ui/src/api/controlPanelApi.ts +226 -0
  141. package/ui/src/dashboard/builtInWidgets.tsx +5 -1
  142. package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
  143. package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
  144. package/ui/src/dashboard/widgets/index.ts +2 -0
  145. package/ui/src/pages/AuthPage.tsx +986 -142
  146. package/ui/src/pages/IntegrationsPage.tsx +288 -0
  147. package/ui/src/pages/RateLimitPage.tsx +292 -0
  148. package/dist-ui/assets/index-BY8OxNgO.js +0 -465
  149. package/dist-ui/assets/index-BY8OxNgO.js.map +0 -1
@@ -255,6 +255,7 @@ export interface PluginDetailResponse {
255
255
  // ==================
256
256
 
257
257
  export type AuthPluginState = 'disabled' | 'enabled' | 'error';
258
+ export type AuthAdapterType = 'auth0' | 'supabase' | 'supertokens' | 'basic';
258
259
 
259
260
  export interface AuthConfigStatus {
260
261
  state: AuthPluginState;
@@ -262,6 +263,118 @@ export interface AuthConfigStatus {
262
263
  error?: string;
263
264
  missingVars?: string[];
264
265
  config?: Record<string, string>;
266
+ /** Runtime config from database (if available) */
267
+ runtimeConfig?: RuntimeAuthConfig;
268
+ }
269
+
270
+ export interface RuntimeAuthConfig {
271
+ adapter: AuthAdapterType | null;
272
+ config: {
273
+ auth0?: Auth0AdapterConfig;
274
+ supabase?: SupabaseAdapterConfig;
275
+ supertokens?: SupertokensAdapterConfig;
276
+ basic?: BasicAdapterConfig;
277
+ };
278
+ settings: {
279
+ authRequired?: boolean;
280
+ excludePaths?: string[];
281
+ debug?: boolean;
282
+ };
283
+ updatedAt: string;
284
+ updatedBy?: string;
285
+ }
286
+
287
+ export interface Auth0AdapterConfig {
288
+ domain: string;
289
+ clientId: string;
290
+ clientSecret: string;
291
+ baseUrl: string;
292
+ secret: string;
293
+ audience?: string;
294
+ scopes?: string[];
295
+ allowedRoles?: string[];
296
+ allowedDomains?: string[];
297
+ }
298
+
299
+ export interface SupabaseAdapterConfig {
300
+ url: string;
301
+ anonKey: string;
302
+ }
303
+
304
+ export interface BasicAdapterConfig {
305
+ username: string;
306
+ password: string;
307
+ realm?: string;
308
+ }
309
+
310
+ export interface SupertokensAdapterConfig {
311
+ connectionUri: string;
312
+ apiKey?: string;
313
+ appName: string;
314
+ apiDomain: string;
315
+ websiteDomain: string;
316
+ apiBasePath?: string;
317
+ websiteBasePath?: string;
318
+ enableEmailPassword?: boolean;
319
+ socialProviders?: {
320
+ google?: { clientId: string; clientSecret: string };
321
+ apple?: { clientId: string; clientSecret: string; keyId: string; teamId: string };
322
+ github?: { clientId: string; clientSecret: string };
323
+ };
324
+ }
325
+
326
+ export interface UpdateAuthConfigRequest {
327
+ adapter: AuthAdapterType;
328
+ config: Record<string, unknown>;
329
+ settings?: {
330
+ authRequired?: boolean;
331
+ excludePaths?: string[];
332
+ };
333
+ }
334
+
335
+ export interface TestProviderRequest {
336
+ adapter: AuthAdapterType;
337
+ config: Record<string, unknown>;
338
+ provider?: 'google' | 'github' | 'apple';
339
+ }
340
+
341
+ export interface TestProviderResponse {
342
+ success: boolean;
343
+ message: string;
344
+ details?: {
345
+ latency?: number;
346
+ version?: string;
347
+ };
348
+ }
349
+
350
+ // ==================
351
+ // Rate Limit Config Types
352
+ // ==================
353
+
354
+ export type RateLimitStrategy = 'sliding-window' | 'fixed-window' | 'token-bucket';
355
+
356
+ export interface RateLimitConfig {
357
+ windowMs: number;
358
+ maxRequests: number;
359
+ strategy: RateLimitStrategy;
360
+ cleanupEnabled: boolean;
361
+ cleanupIntervalMs: number;
362
+ store: string;
363
+ cache: string;
364
+ cacheAvailable: boolean;
365
+ }
366
+
367
+ export interface RateLimitConfigUpdateRequest {
368
+ windowMs?: number;
369
+ maxRequests?: number;
370
+ strategy?: RateLimitStrategy;
371
+ cleanupEnabled?: boolean;
372
+ cleanupIntervalMs?: number;
373
+ }
374
+
375
+ export interface RateLimitConfigUpdateResponse {
376
+ success: boolean;
377
+ config: RateLimitConfig;
265
378
  }
266
379
 
267
380
  class ControlPanelApi {
@@ -279,6 +392,33 @@ class ControlPanelApi {
279
392
  this.baseUrl = baseUrl;
280
393
  }
281
394
 
395
+ /**
396
+ * Get the base URL for API requests.
397
+ */
398
+ getBaseUrl(): string {
399
+ return this.baseUrl;
400
+ }
401
+
402
+ /**
403
+ * Generic fetch method for API requests.
404
+ * Automatically prepends the base URL and /api prefix.
405
+ */
406
+ async fetch<T = unknown>(path: string, options?: RequestInit): Promise<T> {
407
+ const url = `${this.baseUrl}/api${path.startsWith('/') ? path : `/${path}`}`;
408
+ const response = await fetch(url, {
409
+ ...options,
410
+ headers: {
411
+ 'Content-Type': 'application/json',
412
+ ...options?.headers,
413
+ },
414
+ });
415
+ if (!response.ok) {
416
+ const error = await response.json().catch(() => ({}));
417
+ throw new Error(error.error || error.message || `Request failed: ${response.statusText}`);
418
+ }
419
+ return response.json();
420
+ }
421
+
282
422
  // ==================
283
423
  // Plugin Feature Detection
284
424
  // ==================
@@ -595,6 +735,92 @@ class ControlPanelApi {
595
735
  }
596
736
  return response.json();
597
737
  }
738
+
739
+ /**
740
+ * Update auth configuration (save to database for hot-reload)
741
+ */
742
+ async updateAuthConfig(request: UpdateAuthConfigRequest): Promise<{ success: boolean; message: string }> {
743
+ const response = await fetch(`${this.baseUrl}/api/auth/config`, {
744
+ method: 'PUT',
745
+ headers: { 'Content-Type': 'application/json' },
746
+ body: JSON.stringify(request),
747
+ });
748
+ if (!response.ok) {
749
+ const error = await response.json().catch(() => ({}));
750
+ throw new Error(error.error || `Auth config update failed: ${response.statusText}`);
751
+ }
752
+ return response.json();
753
+ }
754
+
755
+ /**
756
+ * Delete auth configuration (revert to environment variables)
757
+ */
758
+ async deleteAuthConfig(): Promise<{ success: boolean; message: string }> {
759
+ const response = await fetch(`${this.baseUrl}/api/auth/config`, {
760
+ method: 'DELETE',
761
+ });
762
+ if (!response.ok) {
763
+ const error = await response.json().catch(() => ({}));
764
+ throw new Error(error.error || `Auth config delete failed: ${response.statusText}`);
765
+ }
766
+ return response.json();
767
+ }
768
+
769
+ /**
770
+ * Test auth provider connection without saving
771
+ */
772
+ async testAuthProvider(request: TestProviderRequest): Promise<TestProviderResponse> {
773
+ const response = await fetch(`${this.baseUrl}/api/auth/test-provider`, {
774
+ method: 'POST',
775
+ headers: { 'Content-Type': 'application/json' },
776
+ body: JSON.stringify(request),
777
+ });
778
+ if (!response.ok) {
779
+ const error = await response.json().catch(() => ({}));
780
+ throw new Error(error.error || `Provider test failed: ${response.statusText}`);
781
+ }
782
+ return response.json();
783
+ }
784
+
785
+ /**
786
+ * Test current auth provider connection (uses existing env/runtime config)
787
+ */
788
+ async testCurrentAuthProvider(): Promise<TestProviderResponse> {
789
+ const response = await fetch(`${this.baseUrl}/api/auth/test-current`, {
790
+ method: 'POST',
791
+ headers: { 'Content-Type': 'application/json' },
792
+ });
793
+ if (!response.ok) {
794
+ const error = await response.json().catch(() => ({}));
795
+ throw new Error(error.error || `Provider test failed: ${response.statusText}`);
796
+ }
797
+ return response.json();
798
+ }
799
+
800
+ // ==================
801
+ // Rate Limit Config API
802
+ // ==================
803
+
804
+ async getRateLimitConfig(): Promise<RateLimitConfig> {
805
+ const response = await fetch(`${this.baseUrl}/api/rate-limit/config`);
806
+ if (!response.ok) {
807
+ throw new Error(`Rate limit config request failed: ${response.statusText}`);
808
+ }
809
+ return response.json();
810
+ }
811
+
812
+ async updateRateLimitConfig(updates: RateLimitConfigUpdateRequest): Promise<RateLimitConfigUpdateResponse> {
813
+ const response = await fetch(`${this.baseUrl}/api/rate-limit/config`, {
814
+ method: 'PUT',
815
+ headers: { 'Content-Type': 'application/json' },
816
+ body: JSON.stringify(updates),
817
+ });
818
+ if (!response.ok) {
819
+ const error = await response.json().catch(() => ({}));
820
+ throw new Error(error.error || `Rate limit config update failed: ${response.statusText}`);
821
+ }
822
+ return response.json();
823
+ }
598
824
  }
599
825
 
600
826
  export const api = new ControlPanelApi();
@@ -10,7 +10,7 @@
10
10
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
11
11
  */
12
12
 
13
- import { ServiceHealthWidget } from './widgets';
13
+ import { ServiceHealthWidget, IntegrationStatusWidget, AuthStatusWidget } from './widgets';
14
14
  import type { WidgetComponent } from './WidgetComponentRegistry';
15
15
 
16
16
  /**
@@ -19,6 +19,8 @@ import type { WidgetComponent } from './WidgetComponentRegistry';
19
19
  */
20
20
  export const builtInWidgetComponents: Record<string, React.ComponentType> = {
21
21
  ServiceHealthWidget: ServiceHealthWidget,
22
+ IntegrationStatusWidget: IntegrationStatusWidget,
23
+ AuthStatusWidget: AuthStatusWidget,
22
24
  };
23
25
 
24
26
  /**
@@ -31,5 +33,7 @@ export const builtInWidgetComponents: Record<string, React.ComponentType> = {
31
33
  export function getBuiltInWidgetComponents(): WidgetComponent[] {
32
34
  return [
33
35
  { name: 'ServiceHealthWidget', component: ServiceHealthWidget },
36
+ { name: 'IntegrationStatusWidget', component: IntegrationStatusWidget },
37
+ { name: 'AuthStatusWidget', component: AuthStatusWidget },
34
38
  ];
35
39
  }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Auth Status Widget
3
+ *
4
+ * Displays the authentication plugin status on the dashboard.
5
+ * Shows whether auth is enabled, the adapter type, and configuration status.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import { useState, useEffect } from 'react';
11
+ import { Box, Typography, Chip, CircularProgress, Alert } from '@mui/material';
12
+ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
13
+ import ErrorIcon from '@mui/icons-material/Error';
14
+ import BlockIcon from '@mui/icons-material/Block';
15
+ import { api } from '../../api/controlPanelApi';
16
+
17
+ interface AuthStatus {
18
+ state: 'enabled' | 'disabled' | 'error';
19
+ adapter: string | null;
20
+ error?: string;
21
+ missingVars?: string[];
22
+ }
23
+
24
+ const adapterLabels: Record<string, string> = {
25
+ supertokens: 'SuperTokens',
26
+ auth0: 'Auth0',
27
+ supabase: 'Supabase',
28
+ basic: 'Basic Auth',
29
+ };
30
+
31
+ export function AuthStatusWidget() {
32
+ const [status, setStatus] = useState<AuthStatus | null>(null);
33
+ const [loading, setLoading] = useState(true);
34
+ const [error, setError] = useState<string | null>(null);
35
+
36
+ useEffect(() => {
37
+ const fetchStatus = async () => {
38
+ try {
39
+ const data = await api.fetch<AuthStatus>('/auth/config/status');
40
+ setStatus(data);
41
+ } catch (err) {
42
+ setError(err instanceof Error ? err.message : 'Failed to fetch auth status');
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ };
47
+
48
+ fetchStatus();
49
+ }, []);
50
+
51
+ if (loading) {
52
+ return (
53
+ <Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
54
+ <CircularProgress size={20} />
55
+ </Box>
56
+ );
57
+ }
58
+
59
+ if (error) {
60
+ return (
61
+ <Alert severity="warning" sx={{ py: 0.5, fontSize: 13 }}>
62
+ Unable to load auth status
63
+ </Alert>
64
+ );
65
+ }
66
+
67
+ if (!status) return null;
68
+
69
+ const getStateIcon = () => {
70
+ switch (status.state) {
71
+ case 'enabled':
72
+ return <CheckCircleIcon sx={{ color: 'var(--theme-success)', fontSize: 32 }} />;
73
+ case 'error':
74
+ return <ErrorIcon sx={{ color: 'var(--theme-error)', fontSize: 32 }} />;
75
+ case 'disabled':
76
+ default:
77
+ return <BlockIcon sx={{ color: 'var(--theme-text-secondary)', fontSize: 32 }} />;
78
+ }
79
+ };
80
+
81
+ const getStateColor = () => {
82
+ switch (status.state) {
83
+ case 'enabled':
84
+ return 'var(--theme-success)';
85
+ case 'error':
86
+ return 'var(--theme-error)';
87
+ case 'disabled':
88
+ default:
89
+ return 'var(--theme-text-secondary)';
90
+ }
91
+ };
92
+
93
+ return (
94
+ <Box
95
+ sx={{
96
+ bgcolor: 'var(--theme-surface)',
97
+ borderRadius: 2,
98
+ p: 2,
99
+ border: '1px solid var(--theme-border)',
100
+ }}
101
+ >
102
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
103
+ {getStateIcon()}
104
+ <Box sx={{ flex: 1 }}>
105
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
106
+ <Typography variant="subtitle1" sx={{ color: 'var(--theme-text-primary)', fontWeight: 600 }}>
107
+ {status.state === 'enabled' && status.adapter
108
+ ? adapterLabels[status.adapter] || status.adapter
109
+ : status.state === 'disabled'
110
+ ? 'Not Configured'
111
+ : 'Configuration Error'}
112
+ </Typography>
113
+ <Chip
114
+ label={status.state.toUpperCase()}
115
+ size="small"
116
+ sx={{
117
+ bgcolor: `${getStateColor()}20`,
118
+ color: getStateColor(),
119
+ fontWeight: 600,
120
+ fontSize: 10,
121
+ height: 20,
122
+ }}
123
+ />
124
+ </Box>
125
+ <Typography variant="body2" sx={{ color: 'var(--theme-text-secondary)' }}>
126
+ {status.state === 'enabled'
127
+ ? 'Authentication is active'
128
+ : status.state === 'disabled'
129
+ ? 'Set AUTH_ADAPTER environment variable'
130
+ : status.error || 'Check configuration'}
131
+ </Typography>
132
+ </Box>
133
+ </Box>
134
+
135
+ {/* Missing vars warning */}
136
+ {status.missingVars && status.missingVars.length > 0 && (
137
+ <Alert severity="warning" sx={{ mt: 2, py: 0.5, '& .MuiAlert-message': { fontSize: 12 } }}>
138
+ Missing: {status.missingVars.join(', ')}
139
+ </Alert>
140
+ )}
141
+ </Box>
142
+ );
143
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Integration Status Widget
3
+ *
4
+ * Displays the status of configured integrations with their connection status.
5
+ * Used in the dashboard to show a quick overview of integration health.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import { useState, useEffect } from 'react';
11
+ import { Box, Typography, Chip, CircularProgress, Alert } from '@mui/material';
12
+ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
13
+ import ErrorIcon from '@mui/icons-material/Error';
14
+ import { api } from '../../api/controlPanelApi';
15
+
16
+ interface Integration {
17
+ id: string;
18
+ name: string;
19
+ description: string;
20
+ configured: boolean;
21
+ }
22
+
23
+ interface IntegrationsConfig {
24
+ integrations: Integration[];
25
+ stats: {
26
+ totalRequests: number;
27
+ };
28
+ }
29
+
30
+ export function IntegrationStatusWidget() {
31
+ const [config, setConfig] = useState<IntegrationsConfig | null>(null);
32
+ const [loading, setLoading] = useState(true);
33
+ const [error, setError] = useState<string | null>(null);
34
+
35
+ useEffect(() => {
36
+ const fetchConfig = async () => {
37
+ try {
38
+ const data = await api.fetch<IntegrationsConfig>('/ai-proxy/config');
39
+ setConfig(data);
40
+ } catch (err) {
41
+ setError(err instanceof Error ? err.message : 'Failed to fetch integrations');
42
+ } finally {
43
+ setLoading(false);
44
+ }
45
+ };
46
+
47
+ fetchConfig();
48
+ }, []);
49
+
50
+ if (loading) {
51
+ return (
52
+ <Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
53
+ <CircularProgress size={20} />
54
+ </Box>
55
+ );
56
+ }
57
+
58
+ if (error) {
59
+ return (
60
+ <Alert severity="warning" sx={{ py: 0.5, fontSize: 13 }}>
61
+ Unable to load integrations
62
+ </Alert>
63
+ );
64
+ }
65
+
66
+ if (!config) return null;
67
+
68
+ const configuredCount = config.integrations.filter((i) => i.configured).length;
69
+ const totalCount = config.integrations.length;
70
+
71
+ return (
72
+ <Box
73
+ sx={{
74
+ bgcolor: 'var(--theme-surface)',
75
+ borderRadius: 2,
76
+ p: 2,
77
+ border: '1px solid var(--theme-border)',
78
+ }}
79
+ >
80
+ {/* Summary */}
81
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
82
+ <Typography variant="subtitle2" sx={{ color: 'var(--theme-text-secondary)' }}>
83
+ {configuredCount} of {totalCount} configured
84
+ </Typography>
85
+ <Typography variant="subtitle2" sx={{ color: 'var(--theme-text-secondary)' }}>
86
+ {config.stats.totalRequests} requests
87
+ </Typography>
88
+ </Box>
89
+
90
+ {/* Integration List */}
91
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
92
+ {config.integrations.map((integration) => (
93
+ <Box
94
+ key={integration.id}
95
+ sx={{
96
+ display: 'flex',
97
+ alignItems: 'center',
98
+ justifyContent: 'space-between',
99
+ p: 1.5,
100
+ bgcolor: 'var(--theme-background)',
101
+ borderRadius: 1,
102
+ }}
103
+ >
104
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
105
+ {integration.configured ? (
106
+ <CheckCircleIcon sx={{ color: 'var(--theme-success)', fontSize: 18 }} />
107
+ ) : (
108
+ <ErrorIcon sx={{ color: 'var(--theme-text-secondary)', fontSize: 18 }} />
109
+ )}
110
+ <Box>
111
+ <Typography variant="body2" sx={{ color: 'var(--theme-text-primary)', fontWeight: 500 }}>
112
+ {integration.name}
113
+ </Typography>
114
+ <Typography variant="caption" sx={{ color: 'var(--theme-text-secondary)' }}>
115
+ {integration.description}
116
+ </Typography>
117
+ </Box>
118
+ </Box>
119
+ <Chip
120
+ label={integration.configured ? 'Connected' : 'Not Configured'}
121
+ size="small"
122
+ sx={{
123
+ bgcolor: integration.configured ? 'var(--theme-success)20' : 'transparent',
124
+ color: integration.configured ? 'var(--theme-success)' : 'var(--theme-text-secondary)',
125
+ border: integration.configured ? 'none' : '1px solid var(--theme-border)',
126
+ fontWeight: 500,
127
+ fontSize: 11,
128
+ }}
129
+ />
130
+ </Box>
131
+ ))}
132
+ </Box>
133
+ </Box>
134
+ );
135
+ }
@@ -5,3 +5,5 @@
5
5
  */
6
6
 
7
7
  export { ServiceHealthWidget } from './ServiceHealthWidget';
8
+ export { IntegrationStatusWidget } from './IntegrationStatusWidget';
9
+ export { AuthStatusWidget } from './AuthStatusWidget';