@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.
- package/README.md +157 -0
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +114 -0
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/types.d.ts +19 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
- package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.js +166 -0
- package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
- package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
- package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
- package/dist/plugins/auth/config-store.d.ts +11 -0
- package/dist/plugins/auth/config-store.d.ts.map +1 -0
- package/dist/plugins/auth/config-store.js +232 -0
- package/dist/plugins/auth/config-store.js.map +1 -0
- package/dist/plugins/auth/config-store.test.d.ts +7 -0
- package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
- package/dist/plugins/auth/config-store.test.js +299 -0
- package/dist/plugins/auth/config-store.test.js.map +1 -0
- package/dist/plugins/auth/env-config.d.ts +51 -1
- package/dist/plugins/auth/env-config.d.ts.map +1 -1
- package/dist/plugins/auth/env-config.js +640 -7
- package/dist/plugins/auth/env-config.js.map +1 -1
- package/dist/plugins/auth/index.d.ts +6 -2
- package/dist/plugins/auth/index.d.ts.map +1 -1
- package/dist/plugins/auth/index.js +5 -1
- package/dist/plugins/auth/index.js.map +1 -1
- package/dist/plugins/auth/types.d.ts +106 -0
- package/dist/plugins/auth/types.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +4 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +3 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
- package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
- package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
- package/dist/plugins/rate-limit/cleanup.js +72 -0
- package/dist/plugins/rate-limit/cleanup.js.map +1 -0
- package/dist/plugins/rate-limit/env-config.d.ts +91 -0
- package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
- package/dist/plugins/rate-limit/env-config.js +318 -0
- package/dist/plugins/rate-limit/env-config.js.map +1 -0
- package/dist/plugins/rate-limit/index.d.ts +76 -0
- package/dist/plugins/rate-limit/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/index.js +79 -0
- package/dist/plugins/rate-limit/index.js.map +1 -0
- package/dist/plugins/rate-limit/middleware.d.ts +40 -0
- package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
- package/dist/plugins/rate-limit/middleware.js +169 -0
- package/dist/plugins/rate-limit/middleware.js.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
- package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
- package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
- package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
- package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
- package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
- package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
- package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/index.js +8 -0
- package/dist/plugins/rate-limit/stores/index.js.map +1 -0
- package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
- package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
- package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
- package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/index.js +27 -0
- package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
- package/dist/plugins/rate-limit/types.d.ts +265 -0
- package/dist/plugins/rate-limit/types.d.ts.map +1 -0
- package/dist/plugins/rate-limit/types.js +9 -0
- package/dist/plugins/rate-limit/types.js.map +1 -0
- package/dist-ui/assets/index-D7DoZ9rL.js +478 -0
- package/dist-ui/assets/index-D7DoZ9rL.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +141 -0
- package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
- package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +2 -0
- package/dist-ui-lib/index.js +3332 -2343
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
- package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
- package/package.json +1 -1
- package/src/core/control-panel.ts +128 -0
- package/src/core/types.ts +17 -0
- package/src/index.ts +38 -0
- package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
- package/src/plugins/auth/adapter-wrapper.ts +205 -0
- package/src/plugins/auth/config-store.test.ts +417 -0
- package/src/plugins/auth/config-store.ts +305 -0
- package/src/plugins/auth/env-config.ts +714 -7
- package/src/plugins/auth/index.ts +22 -1
- package/src/plugins/auth/types.ts +138 -0
- package/src/plugins/index.ts +49 -0
- package/src/plugins/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
- package/src/plugins/rate-limit/cleanup.ts +117 -0
- package/src/plugins/rate-limit/env-config.ts +400 -0
- package/src/plugins/rate-limit/index.ts +128 -0
- package/src/plugins/rate-limit/middleware.ts +212 -0
- package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
- package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
- package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
- package/src/plugins/rate-limit/stores/index.ts +8 -0
- package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
- package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
- package/src/plugins/rate-limit/strategies/index.ts +30 -0
- package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
- package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
- package/src/plugins/rate-limit/types.ts +338 -0
- package/ui/src/App.tsx +32 -14
- package/ui/src/api/controlPanelApi.ts +226 -0
- package/ui/src/dashboard/builtInWidgets.tsx +5 -1
- package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
- package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
- package/ui/src/dashboard/widgets/index.ts +2 -0
- package/ui/src/pages/AuthPage.tsx +986 -142
- package/ui/src/pages/IntegrationsPage.tsx +288 -0
- package/ui/src/pages/RateLimitPage.tsx +292 -0
- package/dist-ui/assets/index-BY8OxNgO.js +0 -465
- 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
|
+
}
|