@qwickapps/server 1.5.1 → 1.6.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 (135) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +41 -0
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/guards.d.ts.map +1 -1
  6. package/dist/core/guards.js +77 -0
  7. package/dist/core/guards.js.map +1 -1
  8. package/dist/core/health-manager.d.ts +4 -0
  9. package/dist/core/health-manager.d.ts.map +1 -1
  10. package/dist/core/health-manager.js +6 -1
  11. package/dist/core/health-manager.js.map +1 -1
  12. package/dist/core/plugin-registry.d.ts +55 -5
  13. package/dist/core/plugin-registry.d.ts.map +1 -1
  14. package/dist/core/plugin-registry.js +57 -19
  15. package/dist/core/plugin-registry.js.map +1 -1
  16. package/dist/core/types.d.ts +2 -0
  17. package/dist/core/types.d.ts.map +1 -1
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/plugins/api-keys/api-keys-plugin.d.ts +46 -0
  23. package/dist/plugins/api-keys/api-keys-plugin.d.ts.map +1 -0
  24. package/dist/plugins/api-keys/api-keys-plugin.js +329 -0
  25. package/dist/plugins/api-keys/api-keys-plugin.js.map +1 -0
  26. package/dist/plugins/api-keys/index.d.ts +14 -0
  27. package/dist/plugins/api-keys/index.d.ts.map +1 -0
  28. package/dist/plugins/api-keys/index.js +17 -0
  29. package/dist/plugins/api-keys/index.js.map +1 -0
  30. package/dist/plugins/api-keys/middleware/bearer-token-auth.d.ts +74 -0
  31. package/dist/plugins/api-keys/middleware/bearer-token-auth.d.ts.map +1 -0
  32. package/dist/plugins/api-keys/middleware/bearer-token-auth.js +201 -0
  33. package/dist/plugins/api-keys/middleware/bearer-token-auth.js.map +1 -0
  34. package/dist/plugins/api-keys/middleware/index.d.ts +7 -0
  35. package/dist/plugins/api-keys/middleware/index.d.ts.map +1 -0
  36. package/dist/plugins/api-keys/middleware/index.js +7 -0
  37. package/dist/plugins/api-keys/middleware/index.js.map +1 -0
  38. package/dist/plugins/api-keys/stores/index.d.ts +7 -0
  39. package/dist/plugins/api-keys/stores/index.d.ts.map +1 -0
  40. package/dist/plugins/api-keys/stores/index.js +7 -0
  41. package/dist/plugins/api-keys/stores/index.js.map +1 -0
  42. package/dist/plugins/api-keys/stores/postgres-store.d.ts +34 -0
  43. package/dist/plugins/api-keys/stores/postgres-store.d.ts.map +1 -0
  44. package/dist/plugins/api-keys/stores/postgres-store.js +360 -0
  45. package/dist/plugins/api-keys/stores/postgres-store.js.map +1 -0
  46. package/dist/plugins/api-keys/types.d.ts +268 -0
  47. package/dist/plugins/api-keys/types.d.ts.map +1 -0
  48. package/dist/plugins/api-keys/types.js +56 -0
  49. package/dist/plugins/api-keys/types.js.map +1 -0
  50. package/dist/plugins/auth/auth-plugin.d.ts.map +1 -1
  51. package/dist/plugins/auth/auth-plugin.js +17 -1
  52. package/dist/plugins/auth/auth-plugin.js.map +1 -1
  53. package/dist/plugins/auth/auth-plugin.test.js +133 -0
  54. package/dist/plugins/auth/auth-plugin.test.js.map +1 -1
  55. package/dist/plugins/auth/env-config.d.ts.map +1 -1
  56. package/dist/plugins/auth/env-config.js +6 -2
  57. package/dist/plugins/auth/env-config.js.map +1 -1
  58. package/dist/plugins/auth/types.d.ts +10 -0
  59. package/dist/plugins/auth/types.d.ts.map +1 -1
  60. package/dist/plugins/auth/types.js.map +1 -1
  61. package/dist/plugins/devices/__tests__/token-utils.test.js +4 -2
  62. package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -1
  63. package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
  64. package/dist/plugins/frontend-app-plugin.js +21 -4
  65. package/dist/plugins/frontend-app-plugin.js.map +1 -1
  66. package/dist/plugins/index.d.ts +2 -0
  67. package/dist/plugins/index.d.ts.map +1 -1
  68. package/dist/plugins/index.js +2 -0
  69. package/dist/plugins/index.js.map +1 -1
  70. package/dist/plugins/qwickbrain/index.d.ts +25 -0
  71. package/dist/plugins/qwickbrain/index.d.ts.map +1 -0
  72. package/dist/plugins/qwickbrain/index.js +24 -0
  73. package/dist/plugins/qwickbrain/index.js.map +1 -0
  74. package/dist/plugins/qwickbrain/qwickbrain-plugin.d.ts +23 -0
  75. package/dist/plugins/qwickbrain/qwickbrain-plugin.d.ts.map +1 -0
  76. package/dist/plugins/qwickbrain/qwickbrain-plugin.js +528 -0
  77. package/dist/plugins/qwickbrain/qwickbrain-plugin.js.map +1 -0
  78. package/dist/plugins/qwickbrain/types.d.ts +131 -0
  79. package/dist/plugins/qwickbrain/types.d.ts.map +1 -0
  80. package/dist/plugins/qwickbrain/types.js +9 -0
  81. package/dist/plugins/qwickbrain/types.js.map +1 -0
  82. package/dist/plugins/users/__tests__/postgres-store.test.js +1 -0
  83. package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -1
  84. package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
  85. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
  86. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
  87. package/dist/plugins/users/stores/postgres-store.js +59 -1
  88. package/dist/plugins/users/stores/postgres-store.js.map +1 -1
  89. package/dist/plugins/users/types.d.ts +22 -0
  90. package/dist/plugins/users/types.d.ts.map +1 -1
  91. package/dist-ui/assets/index-5nX8fM1a.js +469 -0
  92. package/dist-ui/assets/index-5nX8fM1a.js.map +1 -0
  93. package/dist-ui/index.html +1 -1
  94. package/dist-ui-lib/api/controlPanelApi.d.ts +68 -0
  95. package/dist-ui-lib/components/index.d.ts +2 -1
  96. package/dist-ui-lib/index.js +2642 -2281
  97. package/dist-ui-lib/index.js.map +1 -1
  98. package/dist-ui-lib/pages/APIKeysPage.d.ts +13 -0
  99. package/dist-ui-lib/pages/AcceptInvitationPage.d.ts +28 -0
  100. package/package.json +3 -2
  101. package/src/core/control-panel.ts +47 -0
  102. package/src/core/guards.ts +89 -0
  103. package/src/core/health-manager.ts +6 -1
  104. package/src/core/plugin-registry.ts +123 -25
  105. package/src/core/types.ts +2 -0
  106. package/src/index.ts +11 -0
  107. package/src/plugins/api-keys/api-keys-plugin.ts +397 -0
  108. package/src/plugins/api-keys/index.ts +49 -0
  109. package/src/plugins/api-keys/middleware/bearer-token-auth.ts +250 -0
  110. package/src/plugins/api-keys/middleware/index.ts +12 -0
  111. package/src/plugins/api-keys/stores/index.ts +7 -0
  112. package/src/plugins/api-keys/stores/postgres-store.ts +487 -0
  113. package/src/plugins/api-keys/types.ts +243 -0
  114. package/src/plugins/auth/auth-plugin.test.ts +167 -0
  115. package/src/plugins/auth/auth-plugin.ts +17 -1
  116. package/src/plugins/auth/env-config.ts +6 -2
  117. package/src/plugins/auth/types.ts +10 -0
  118. package/src/plugins/devices/__tests__/token-utils.test.ts +4 -2
  119. package/src/plugins/frontend-app-plugin.ts +24 -4
  120. package/src/plugins/index.ts +15 -0
  121. package/src/plugins/qwickbrain/index.ts +33 -0
  122. package/src/plugins/qwickbrain/qwickbrain-plugin.ts +642 -0
  123. package/src/plugins/qwickbrain/types.ts +146 -0
  124. package/src/plugins/users/__tests__/postgres-store.test.ts +1 -0
  125. package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
  126. package/src/plugins/users/stores/postgres-store.ts +69 -0
  127. package/src/plugins/users/types.ts +25 -0
  128. package/ui/src/App.tsx +6 -1
  129. package/ui/src/api/controlPanelApi.ts +206 -37
  130. package/ui/src/components/index.ts +6 -0
  131. package/ui/src/pages/APIKeysPage.tsx +661 -0
  132. package/ui/src/pages/AcceptInvitationPage.tsx +169 -0
  133. package/ui/src/pages/UsersPage.tsx +225 -2
  134. package/dist-ui/assets/index-CynOqPkb.js +0 -469
  135. package/dist-ui/assets/index-CynOqPkb.js.map +0 -1
@@ -81,10 +81,15 @@ export interface LogSource {
81
81
  // ==================
82
82
  // Users API Types
83
83
  // ==================
84
+ export type UserStatus = 'invited' | 'active' | 'suspended';
85
+
84
86
  export interface User {
85
87
  id: string;
86
88
  email: string;
87
89
  name?: string;
90
+ status: UserStatus;
91
+ invitation_token?: string;
92
+ invitation_expires_at?: string;
88
93
  created_at?: string;
89
94
  updated_at?: string;
90
95
  last_login?: string;
@@ -98,6 +103,30 @@ export interface UsersResponse {
98
103
  limit: number;
99
104
  }
100
105
 
106
+ export interface InviteUserRequest {
107
+ email: string;
108
+ name?: string;
109
+ role?: string;
110
+ metadata?: Record<string, unknown>;
111
+ expiresInDays?: number;
112
+ }
113
+
114
+ export interface InvitationResponse {
115
+ user: User;
116
+ token: string;
117
+ inviteLink: string;
118
+ expiresAt: string;
119
+ }
120
+
121
+ export interface AcceptInvitationRequest {
122
+ token: string;
123
+ }
124
+
125
+ export interface AcceptInvitationResponse {
126
+ success: boolean;
127
+ user: User;
128
+ }
129
+
101
130
  // ==================
102
131
  // Bans API Types
103
132
  // ==================
@@ -116,6 +145,44 @@ export interface BansResponse {
116
145
  total: number;
117
146
  }
118
147
 
148
+ // ==================
149
+ // API Keys Types
150
+ // ==================
151
+ export interface ApiKey {
152
+ id: string;
153
+ name: string;
154
+ key_prefix: string;
155
+ key_type: 'm2m' | 'pat';
156
+ scopes: Array<'read' | 'write' | 'admin'>;
157
+ last_used_at: string | null;
158
+ expires_at: string | null;
159
+ is_active: boolean;
160
+ created_at: string;
161
+ updated_at: string;
162
+ }
163
+
164
+ export interface ApiKeyWithPlaintext extends ApiKey {
165
+ key: string; // Only available on creation
166
+ }
167
+
168
+ export interface ApiKeysResponse {
169
+ keys: ApiKey[];
170
+ }
171
+
172
+ export interface CreateApiKeyRequest {
173
+ name: string;
174
+ key_type: 'm2m' | 'pat';
175
+ scopes: Array<'read' | 'write' | 'admin'>;
176
+ expires_at?: string;
177
+ }
178
+
179
+ export interface UpdateApiKeyRequest {
180
+ name?: string;
181
+ scopes?: Array<'read' | 'write' | 'admin'>;
182
+ expires_at?: string;
183
+ is_active?: boolean;
184
+ }
185
+
119
186
  // ==================
120
187
  // Entitlements API Types
121
188
  // ==================
@@ -444,13 +511,25 @@ class ControlPanelApi {
444
511
  return this.baseUrl;
445
512
  }
446
513
 
514
+ /**
515
+ * Internal fetch wrapper that includes credentials for Basic Auth support.
516
+ * Using 'same-origin' ensures the browser sends stored Basic Auth credentials
517
+ * without embedding them in the URL (which would cause fetch to fail).
518
+ */
519
+ private async _fetch(url: string, options?: RequestInit): Promise<Response> {
520
+ return fetch(url, {
521
+ ...options,
522
+ credentials: 'same-origin',
523
+ });
524
+ }
525
+
447
526
  /**
448
527
  * Generic fetch method for API requests.
449
528
  * Automatically prepends the base URL and /api prefix.
450
529
  */
451
530
  async fetch<T = unknown>(path: string, options?: RequestInit): Promise<T> {
452
531
  const url = `${this.baseUrl}/api${path.startsWith('/') ? path : `/${path}`}`;
453
- const response = await fetch(url, {
532
+ const response = await this._fetch(url, {
454
533
  ...options,
455
534
  headers: {
456
535
  'Content-Type': 'application/json',
@@ -494,7 +573,7 @@ class ControlPanelApi {
494
573
 
495
574
  private async checkEndpoint(path: string): Promise<boolean> {
496
575
  try {
497
- const response = await fetch(`${this.baseUrl}${path}`, { method: 'HEAD' });
576
+ const response = await this._fetch(`${this.baseUrl}${path}`, { method: 'HEAD' });
498
577
  // 200, 401, 403 mean the endpoint exists (might need auth)
499
578
  // 404 means it doesn't exist
500
579
  return response.status !== 404;
@@ -517,7 +596,7 @@ class ControlPanelApi {
517
596
  if (options.page) params.set('page', options.page.toString());
518
597
  if (options.search) params.set('q', options.search);
519
598
 
520
- const response = await fetch(`${this.baseUrl}/api/users?${params}`);
599
+ const response = await this._fetch(`${this.baseUrl}/api/users?${params}`);
521
600
  if (!response.ok) {
522
601
  throw new Error(`Users request failed: ${response.statusText}`);
523
602
  }
@@ -525,19 +604,53 @@ class ControlPanelApi {
525
604
  }
526
605
 
527
606
  async getUserById(id: string): Promise<User> {
528
- const response = await fetch(`${this.baseUrl}/api/users/${id}`);
607
+ const response = await this._fetch(`${this.baseUrl}/api/users/${id}`);
529
608
  if (!response.ok) {
530
609
  throw new Error(`User request failed: ${response.statusText}`);
531
610
  }
532
611
  return response.json();
533
612
  }
534
613
 
614
+ async inviteUser(request: InviteUserRequest): Promise<InvitationResponse> {
615
+ const response = await this._fetch(`${this.baseUrl}/api/users/invite`, {
616
+ method: 'POST',
617
+ headers: { 'Content-Type': 'application/json' },
618
+ body: JSON.stringify(request),
619
+ });
620
+ if (!response.ok) {
621
+ const error = await response.json().catch(() => ({}));
622
+ throw new Error(error.error || `Invite user failed: ${response.statusText}`);
623
+ }
624
+ return response.json();
625
+ }
626
+
627
+ async acceptInvitation(token: string): Promise<AcceptInvitationResponse> {
628
+ const response = await this._fetch(`${this.baseUrl}/api/users/accept-invitation/${encodeURIComponent(token)}`);
629
+ if (!response.ok) {
630
+ const error = await response.json().catch(() => ({}));
631
+ throw new Error(error.error || `Accept invitation failed: ${response.statusText}`);
632
+ }
633
+ return response.json();
634
+ }
635
+
636
+ async getInvitations(): Promise<UsersResponse> {
637
+ const params = new URLSearchParams();
638
+ params.set('status', 'invited');
639
+ params.set('limit', '100');
640
+
641
+ const response = await this._fetch(`${this.baseUrl}/api/users?${params}`);
642
+ if (!response.ok) {
643
+ throw new Error(`Invitations request failed: ${response.statusText}`);
644
+ }
645
+ return response.json();
646
+ }
647
+
535
648
  // ==================
536
649
  // Bans API
537
650
  // ==================
538
651
 
539
652
  async getBans(): Promise<BansResponse> {
540
- const response = await fetch(`${this.baseUrl}/api/bans`);
653
+ const response = await this._fetch(`${this.baseUrl}/api/bans`);
541
654
  if (!response.ok) {
542
655
  throw new Error(`Bans request failed: ${response.statusText}`);
543
656
  }
@@ -553,7 +666,7 @@ class ControlPanelApi {
553
666
  duration = Math.max(0, Math.floor((expiresDate.getTime() - now.getTime()) / 1000));
554
667
  }
555
668
 
556
- const response = await fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`, {
669
+ const response = await this._fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`, {
557
670
  method: 'POST',
558
671
  headers: { 'Content-Type': 'application/json' },
559
672
  body: JSON.stringify({ reason, duration }),
@@ -565,7 +678,7 @@ class ControlPanelApi {
565
678
  }
566
679
 
567
680
  async unbanUser(email: string): Promise<void> {
568
- const response = await fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`, {
681
+ const response = await this._fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`, {
569
682
  method: 'DELETE',
570
683
  });
571
684
  if (!response.ok) {
@@ -574,7 +687,7 @@ class ControlPanelApi {
574
687
  }
575
688
 
576
689
  async checkBan(email: string): Promise<{ banned: boolean; ban?: Ban }> {
577
- const response = await fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`);
690
+ const response = await this._fetch(`${this.baseUrl}/api/bans/email/${encodeURIComponent(email)}`);
578
691
  if (!response.ok) {
579
692
  throw new Error(`Ban check failed: ${response.statusText}`);
580
693
  }
@@ -588,7 +701,7 @@ class ControlPanelApi {
588
701
  // ==================
589
702
 
590
703
  async getEntitlements(email: string): Promise<EntitlementResult> {
591
- const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}`);
704
+ const response = await this._fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}`);
592
705
  if (!response.ok) {
593
706
  throw new Error(`Entitlements request failed: ${response.statusText}`);
594
707
  }
@@ -596,7 +709,7 @@ class ControlPanelApi {
596
709
  }
597
710
 
598
711
  async refreshEntitlements(email: string): Promise<EntitlementResult> {
599
- const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/refresh`, {
712
+ const response = await this._fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/refresh`, {
600
713
  method: 'POST',
601
714
  });
602
715
  if (!response.ok) {
@@ -606,7 +719,7 @@ class ControlPanelApi {
606
719
  }
607
720
 
608
721
  async checkEntitlement(email: string, entitlement: string): Promise<{ has: boolean }> {
609
- const response = await fetch(
722
+ const response = await this._fetch(
610
723
  `${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/check/${encodeURIComponent(entitlement)}`
611
724
  );
612
725
  if (!response.ok) {
@@ -616,7 +729,7 @@ class ControlPanelApi {
616
729
  }
617
730
 
618
731
  async getAvailableEntitlements(): Promise<EntitlementDefinition[]> {
619
- const response = await fetch(`${this.baseUrl}/api/entitlements/available`);
732
+ const response = await this._fetch(`${this.baseUrl}/api/entitlements/available`);
620
733
  if (!response.ok) {
621
734
  throw new Error(`Available entitlements request failed: ${response.statusText}`);
622
735
  }
@@ -625,7 +738,7 @@ class ControlPanelApi {
625
738
  }
626
739
 
627
740
  async grantEntitlement(email: string, entitlement: string): Promise<void> {
628
- const response = await fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}`, {
741
+ const response = await this._fetch(`${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}`, {
629
742
  method: 'POST',
630
743
  headers: { 'Content-Type': 'application/json' },
631
744
  body: JSON.stringify({ entitlement }),
@@ -637,7 +750,7 @@ class ControlPanelApi {
637
750
  }
638
751
 
639
752
  async revokeEntitlement(email: string, entitlement: string): Promise<void> {
640
- const response = await fetch(
753
+ const response = await this._fetch(
641
754
  `${this.baseUrl}/api/entitlements/${encodeURIComponent(email)}/${encodeURIComponent(entitlement)}`,
642
755
  { method: 'DELETE' }
643
756
  );
@@ -647,7 +760,7 @@ class ControlPanelApi {
647
760
  }
648
761
 
649
762
  async invalidateEntitlementCache(email: string): Promise<void> {
650
- const response = await fetch(`${this.baseUrl}/api/entitlements/cache/${encodeURIComponent(email)}`, {
763
+ const response = await this._fetch(`${this.baseUrl}/api/entitlements/cache/${encodeURIComponent(email)}`, {
651
764
  method: 'DELETE',
652
765
  });
653
766
  if (!response.ok) {
@@ -656,7 +769,7 @@ class ControlPanelApi {
656
769
  }
657
770
 
658
771
  async getEntitlementsStatus(): Promise<EntitlementsStatus> {
659
- const response = await fetch(`${this.baseUrl}/api/entitlements/status`);
772
+ const response = await this._fetch(`${this.baseUrl}/api/entitlements/status`);
660
773
  if (!response.ok) {
661
774
  throw new Error(`Entitlements status request failed: ${response.statusText}`);
662
775
  }
@@ -668,7 +781,7 @@ class ControlPanelApi {
668
781
  // ==================
669
782
 
670
783
  async getHealth(): Promise<HealthResponse> {
671
- const response = await fetch(`${this.baseUrl}/api/health`);
784
+ const response = await this._fetch(`${this.baseUrl}/api/health`);
672
785
  if (!response.ok) {
673
786
  throw new Error(`Health check failed: ${response.statusText}`);
674
787
  }
@@ -676,7 +789,7 @@ class ControlPanelApi {
676
789
  }
677
790
 
678
791
  async getInfo(): Promise<InfoResponse> {
679
- const response = await fetch(`${this.baseUrl}/api/info`);
792
+ const response = await this._fetch(`${this.baseUrl}/api/info`);
680
793
  if (!response.ok) {
681
794
  throw new Error(`Info request failed: ${response.statusText}`);
682
795
  }
@@ -684,7 +797,7 @@ class ControlPanelApi {
684
797
  }
685
798
 
686
799
  async getDiagnostics(): Promise<DiagnosticsResponse> {
687
- const response = await fetch(`${this.baseUrl}/api/diagnostics`);
800
+ const response = await this._fetch(`${this.baseUrl}/api/diagnostics`);
688
801
  if (!response.ok) {
689
802
  throw new Error(`Diagnostics request failed: ${response.statusText}`);
690
803
  }
@@ -692,7 +805,7 @@ class ControlPanelApi {
692
805
  }
693
806
 
694
807
  async getConfig(): Promise<ConfigResponse> {
695
- const response = await fetch(`${this.baseUrl}/api/config`);
808
+ const response = await this._fetch(`${this.baseUrl}/api/config`);
696
809
  if (!response.ok) {
697
810
  throw new Error(`Config request failed: ${response.statusText}`);
698
811
  }
@@ -713,7 +826,7 @@ class ControlPanelApi {
713
826
  if (options.limit) params.set('limit', options.limit.toString());
714
827
  if (options.page) params.set('page', options.page.toString());
715
828
 
716
- const response = await fetch(`${this.baseUrl}/api/logs?${params}`);
829
+ const response = await this._fetch(`${this.baseUrl}/api/logs?${params}`);
717
830
  if (!response.ok) {
718
831
  throw new Error(`Logs request failed: ${response.statusText}`);
719
832
  }
@@ -721,7 +834,7 @@ class ControlPanelApi {
721
834
  }
722
835
 
723
836
  async getLogSources(): Promise<LogSource[]> {
724
- const response = await fetch(`${this.baseUrl}/api/logs/sources`);
837
+ const response = await this._fetch(`${this.baseUrl}/api/logs/sources`);
725
838
  if (!response.ok) {
726
839
  throw new Error(`Log sources request failed: ${response.statusText}`);
727
840
  }
@@ -734,7 +847,7 @@ class ControlPanelApi {
734
847
  // ==================
735
848
 
736
849
  async getPlugins(): Promise<PluginsResponse> {
737
- const response = await fetch(`${this.baseUrl}/api/plugins`);
850
+ const response = await this._fetch(`${this.baseUrl}/api/plugins`);
738
851
  if (!response.ok) {
739
852
  throw new Error(`Plugins request failed: ${response.statusText}`);
740
853
  }
@@ -742,7 +855,7 @@ class ControlPanelApi {
742
855
  }
743
856
 
744
857
  async getPluginDetail(id: string): Promise<PluginDetailResponse> {
745
- const response = await fetch(`${this.baseUrl}/api/plugins/${encodeURIComponent(id)}`);
858
+ const response = await this._fetch(`${this.baseUrl}/api/plugins/${encodeURIComponent(id)}`);
746
859
  if (!response.ok) {
747
860
  if (response.status === 404) {
748
861
  throw new Error(`Plugin not found: ${id}`);
@@ -757,7 +870,7 @@ class ControlPanelApi {
757
870
  // ==================
758
871
 
759
872
  async getUiContributions(): Promise<UiContributionsResponse> {
760
- const response = await fetch(`${this.baseUrl}/api/ui-contributions`);
873
+ const response = await this._fetch(`${this.baseUrl}/api/ui-contributions`);
761
874
  if (!response.ok) {
762
875
  throw new Error(`UI contributions request failed: ${response.statusText}`);
763
876
  }
@@ -769,7 +882,7 @@ class ControlPanelApi {
769
882
  // ==================
770
883
 
771
884
  async getAuthConfigStatus(): Promise<AuthConfigStatus> {
772
- const response = await fetch(`${this.baseUrl}/api/auth/config/status`);
885
+ const response = await this._fetch(`${this.baseUrl}/api/auth/config/status`);
773
886
  if (!response.ok) {
774
887
  // Return disabled state if endpoint not available
775
888
  if (response.status === 404) {
@@ -781,7 +894,7 @@ class ControlPanelApi {
781
894
  }
782
895
 
783
896
  async getAuthConfig(): Promise<AuthConfigStatus> {
784
- const response = await fetch(`${this.baseUrl}/api/auth/config`);
897
+ const response = await this._fetch(`${this.baseUrl}/api/auth/config`);
785
898
  if (!response.ok) {
786
899
  if (response.status === 404) {
787
900
  return { state: 'disabled', adapter: null };
@@ -795,7 +908,7 @@ class ControlPanelApi {
795
908
  * Update auth configuration (save to database for hot-reload)
796
909
  */
797
910
  async updateAuthConfig(request: UpdateAuthConfigRequest): Promise<{ success: boolean; message: string }> {
798
- const response = await fetch(`${this.baseUrl}/api/auth/config`, {
911
+ const response = await this._fetch(`${this.baseUrl}/api/auth/config`, {
799
912
  method: 'PUT',
800
913
  headers: { 'Content-Type': 'application/json' },
801
914
  body: JSON.stringify(request),
@@ -811,7 +924,7 @@ class ControlPanelApi {
811
924
  * Delete auth configuration (revert to environment variables)
812
925
  */
813
926
  async deleteAuthConfig(): Promise<{ success: boolean; message: string }> {
814
- const response = await fetch(`${this.baseUrl}/api/auth/config`, {
927
+ const response = await this._fetch(`${this.baseUrl}/api/auth/config`, {
815
928
  method: 'DELETE',
816
929
  });
817
930
  if (!response.ok) {
@@ -825,7 +938,7 @@ class ControlPanelApi {
825
938
  * Test auth provider connection without saving
826
939
  */
827
940
  async testAuthProvider(request: TestProviderRequest): Promise<TestProviderResponse> {
828
- const response = await fetch(`${this.baseUrl}/api/auth/test-provider`, {
941
+ const response = await this._fetch(`${this.baseUrl}/api/auth/test-provider`, {
829
942
  method: 'POST',
830
943
  headers: { 'Content-Type': 'application/json' },
831
944
  body: JSON.stringify(request),
@@ -841,7 +954,7 @@ class ControlPanelApi {
841
954
  * Test current auth provider connection (uses existing env/runtime config)
842
955
  */
843
956
  async testCurrentAuthProvider(): Promise<TestProviderResponse> {
844
- const response = await fetch(`${this.baseUrl}/api/auth/test-current`, {
957
+ const response = await this._fetch(`${this.baseUrl}/api/auth/test-current`, {
845
958
  method: 'POST',
846
959
  headers: { 'Content-Type': 'application/json' },
847
960
  });
@@ -857,7 +970,7 @@ class ControlPanelApi {
857
970
  // ==================
858
971
 
859
972
  async getRateLimitConfig(): Promise<RateLimitConfig> {
860
- const response = await fetch(`${this.baseUrl}/api/rate-limit/config`);
973
+ const response = await this._fetch(`${this.baseUrl}/api/rate-limit/config`);
861
974
  if (!response.ok) {
862
975
  throw new Error(`Rate limit config request failed: ${response.statusText}`);
863
976
  }
@@ -865,7 +978,7 @@ class ControlPanelApi {
865
978
  }
866
979
 
867
980
  async updateRateLimitConfig(updates: RateLimitConfigUpdateRequest): Promise<RateLimitConfigUpdateResponse> {
868
- const response = await fetch(`${this.baseUrl}/api/rate-limit/config`, {
981
+ const response = await this._fetch(`${this.baseUrl}/api/rate-limit/config`, {
869
982
  method: 'PUT',
870
983
  headers: { 'Content-Type': 'application/json' },
871
984
  body: JSON.stringify(updates),
@@ -882,7 +995,7 @@ class ControlPanelApi {
882
995
  // ==================
883
996
 
884
997
  async getNotificationsStats(): Promise<NotificationsStatsResponse> {
885
- const response = await fetch(`${this.baseUrl}/api/notifications/stats`);
998
+ const response = await this._fetch(`${this.baseUrl}/api/notifications/stats`);
886
999
  if (!response.ok) {
887
1000
  throw new Error(`Notifications stats request failed: ${response.statusText}`);
888
1001
  }
@@ -890,7 +1003,7 @@ class ControlPanelApi {
890
1003
  }
891
1004
 
892
1005
  async getNotificationsClients(): Promise<NotificationsClientsResponse> {
893
- const response = await fetch(`${this.baseUrl}/api/notifications/clients`);
1006
+ const response = await this._fetch(`${this.baseUrl}/api/notifications/clients`);
894
1007
  if (!response.ok) {
895
1008
  throw new Error(`Notifications clients request failed: ${response.statusText}`);
896
1009
  }
@@ -898,7 +1011,7 @@ class ControlPanelApi {
898
1011
  }
899
1012
 
900
1013
  async disconnectNotificationsClient(clientId: string): Promise<{ success: boolean }> {
901
- const response = await fetch(`${this.baseUrl}/api/notifications/clients/${encodeURIComponent(clientId)}`, {
1014
+ const response = await this._fetch(`${this.baseUrl}/api/notifications/clients/${encodeURIComponent(clientId)}`, {
902
1015
  method: 'DELETE',
903
1016
  });
904
1017
  if (!response.ok) {
@@ -909,7 +1022,7 @@ class ControlPanelApi {
909
1022
  }
910
1023
 
911
1024
  async forceNotificationsReconnect(): Promise<{ success: boolean; message: string }> {
912
- const response = await fetch(`${this.baseUrl}/api/notifications/reconnect`, {
1025
+ const response = await this._fetch(`${this.baseUrl}/api/notifications/reconnect`, {
913
1026
  method: 'POST',
914
1027
  });
915
1028
  if (!response.ok) {
@@ -918,6 +1031,62 @@ class ControlPanelApi {
918
1031
  }
919
1032
  return response.json();
920
1033
  }
1034
+
1035
+ // ==================
1036
+ // API Keys API
1037
+ // ==================
1038
+
1039
+ async getApiKeys(): Promise<ApiKeysResponse> {
1040
+ const response = await this._fetch(`${this.baseUrl}/api/api-keys`);
1041
+ if (!response.ok) {
1042
+ throw new Error(`API keys request failed: ${response.statusText}`);
1043
+ }
1044
+ return response.json();
1045
+ }
1046
+
1047
+ async createApiKey(request: CreateApiKeyRequest): Promise<ApiKeyWithPlaintext> {
1048
+ const response = await this._fetch(`${this.baseUrl}/api/api-keys`, {
1049
+ method: 'POST',
1050
+ headers: { 'Content-Type': 'application/json' },
1051
+ body: JSON.stringify(request),
1052
+ });
1053
+ if (!response.ok) {
1054
+ const error = await response.json().catch(() => ({}));
1055
+ throw new Error(error.error || `API key creation failed: ${response.statusText}`);
1056
+ }
1057
+ return response.json();
1058
+ }
1059
+
1060
+ async getApiKey(keyId: string): Promise<ApiKey> {
1061
+ const response = await this._fetch(`${this.baseUrl}/api/api-keys/${encodeURIComponent(keyId)}`);
1062
+ if (!response.ok) {
1063
+ throw new Error(`API key request failed: ${response.statusText}`);
1064
+ }
1065
+ return response.json();
1066
+ }
1067
+
1068
+ async updateApiKey(keyId: string, updates: UpdateApiKeyRequest): Promise<ApiKey> {
1069
+ const response = await this._fetch(`${this.baseUrl}/api/api-keys/${encodeURIComponent(keyId)}`, {
1070
+ method: 'PUT',
1071
+ headers: { 'Content-Type': 'application/json' },
1072
+ body: JSON.stringify(updates),
1073
+ });
1074
+ if (!response.ok) {
1075
+ const error = await response.json().catch(() => ({}));
1076
+ throw new Error(error.error || `API key update failed: ${response.statusText}`);
1077
+ }
1078
+ return response.json();
1079
+ }
1080
+
1081
+ async deleteApiKey(keyId: string): Promise<void> {
1082
+ const response = await this._fetch(`${this.baseUrl}/api/api-keys/${encodeURIComponent(keyId)}`, {
1083
+ method: 'DELETE',
1084
+ });
1085
+ if (!response.ok) {
1086
+ const error = await response.json().catch(() => ({}));
1087
+ throw new Error(error.error || `API key deletion failed: ${response.statusText}`);
1088
+ }
1089
+ }
921
1090
  }
922
1091
 
923
1092
  export const api = new ControlPanelApi();
@@ -18,6 +18,7 @@ export { SystemPage } from '../pages/SystemPage';
18
18
  export { NotFoundPage } from '../pages/NotFoundPage';
19
19
  export { UsersPage, type UsersPageProps } from '../pages/UsersPage';
20
20
  export { EntitlementsPage, type EntitlementsPageProps } from '../pages/EntitlementsPage';
21
+ export { AcceptInvitationPage, type AcceptInvitationPageProps } from '../pages/AcceptInvitationPage';
21
22
 
22
23
  // Re-export dashboard widget system (legacy context-based + new plugin-based)
23
24
  export {
@@ -50,8 +51,13 @@ export type {
50
51
  LogsResponse,
51
52
  LogSource,
52
53
  // User management types
54
+ UserStatus,
53
55
  User,
54
56
  UsersResponse,
57
+ InviteUserRequest,
58
+ InvitationResponse,
59
+ AcceptInvitationRequest,
60
+ AcceptInvitationResponse,
55
61
  Ban,
56
62
  BansResponse,
57
63
  EntitlementDefinition,