@littlebearapps/platform-admin-sdk 2.0.0 → 2.1.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 (74) hide show
  1. package/README.md +2 -2
  2. package/dist/templates.d.ts +1 -1
  3. package/dist/templates.js +86 -2
  4. package/package.json +1 -1
  5. package/templates/full/dashboard/src/components/reports/DigestStats.tsx +151 -0
  6. package/templates/full/dashboard/src/components/reports/HealthTrendsReport.tsx +192 -0
  7. package/templates/full/dashboard/src/components/usage/AIModelBreakdown.tsx +364 -0
  8. package/templates/full/dashboard/src/components/usage/unified/Recommendations.tsx +149 -0
  9. package/templates/full/dashboard/src/lib/cloudflare/alerting.ts +486 -0
  10. package/templates/full/dashboard/src/lib/cloudflare/graphql.ts +4785 -0
  11. package/templates/full/dashboard/src/lib/cloudflare/project-registry.ts +451 -0
  12. package/templates/full/dashboard/src/lib/notifications/api.ts +197 -0
  13. package/templates/full/dashboard/src/lib/notifications/types.ts.hbs +97 -0
  14. package/templates/full/dashboard/src/lib/patterns/api.ts +120 -0
  15. package/templates/full/dashboard/src/lib/patterns/types.ts +127 -0
  16. package/templates/full/dashboard/src/lib/reports/types.ts +231 -0
  17. package/templates/full/dashboard/src/lib/search/api.ts +258 -0
  18. package/templates/full/dashboard/src/lib/search/types.ts.hbs +115 -0
  19. package/templates/full/dashboard/src/lib/settings/api.ts.hbs +201 -0
  20. package/templates/full/dashboard/src/lib/settings/types.ts.hbs +104 -0
  21. package/templates/full/dashboard/src/lib/usage/allowance-config.ts.hbs +547 -0
  22. package/templates/full/dashboard/src/lib/usage/providers.ts +331 -0
  23. package/templates/shared/dashboard/src/components/reports/ReportInfoButton.tsx +98 -0
  24. package/templates/shared/dashboard/src/components/usage/react/DashboardShell.tsx +263 -0
  25. package/templates/shared/dashboard/src/components/usage/react/StatusBadge.tsx +77 -0
  26. package/templates/shared/dashboard/src/components/usage/react/UsageChart.tsx +391 -0
  27. package/templates/shared/dashboard/src/components/usage/react/index.ts.hbs +30 -0
  28. package/templates/shared/dashboard/src/components/usage/react/types.ts +137 -0
  29. package/templates/shared/dashboard/src/components/usage/transformers.ts +478 -0
  30. package/templates/shared/dashboard/src/components/usage/unified/AlertBanner.tsx +172 -0
  31. package/templates/shared/dashboard/src/components/usage/unified/HeroCardsRow.tsx +757 -0
  32. package/templates/shared/dashboard/src/components/usage/unified/LiveHeader.tsx +169 -0
  33. package/templates/shared/dashboard/src/components/usage/unified/ProjectsTable.tsx +448 -0
  34. package/templates/shared/dashboard/src/components/usage/unified/ResourceBreakdown.tsx +236 -0
  35. package/templates/shared/dashboard/src/components/usage/unified/Sparkline.tsx +127 -0
  36. package/templates/shared/dashboard/src/components/usage/unified/UnifiedShell.tsx +893 -0
  37. package/templates/shared/dashboard/src/components/usage/unified/index.ts.hbs +50 -0
  38. package/templates/shared/dashboard/src/components/usage/unified/types.ts +416 -0
  39. package/templates/shared/dashboard/src/lib/cloudflare/analytics.ts +310 -0
  40. package/templates/shared/dashboard/src/lib/cloudflare/d1.ts +55 -0
  41. package/templates/shared/dashboard/src/lib/cloudflare/index.ts.hbs +120 -0
  42. package/templates/shared/dashboard/src/lib/infrastructure/types.ts +116 -0
  43. package/templates/shared/dashboard/src/lib/usage/fetchWithDedup.ts +101 -0
  44. package/templates/shared/dashboard/src/lib/usage/index.ts.hbs +12 -0
  45. package/templates/shared/tests/e2e/usage-api.test.ts +909 -0
  46. package/templates/shared/tests/helpers/mock-storage.ts +166 -0
  47. package/templates/shared/tests/integration/kv-cache.test.ts +252 -0
  48. package/templates/shared/tests/integration/platform-usage.test.ts +956 -0
  49. package/templates/shared/tests/unit/billing.test.ts +331 -0
  50. package/templates/shared/tests/unit/cloudflare/graphql.test.ts +217 -0
  51. package/templates/shared/tests/unit/components/usage-transformers.test.ts +473 -0
  52. package/templates/shared/tests/unit/control.test.ts +226 -0
  53. package/templates/shared/tests/unit/cost-calculator.test.ts +141 -0
  54. package/templates/shared/tests/unit/economics.test.ts +365 -0
  55. package/templates/shared/tests/unit/telemetry-sampling.test.ts +401 -0
  56. package/templates/standard/dashboard/src/components/reports/CircuitBreakerReport.tsx +474 -0
  57. package/templates/standard/dashboard/src/components/reports/CostTrendsReport.tsx +229 -0
  58. package/templates/standard/dashboard/src/components/reports/ErrorTrendsReport.tsx +244 -0
  59. package/templates/standard/dashboard/src/components/reports/ProjectHealthTable.tsx +251 -0
  60. package/templates/standard/dashboard/src/components/reports/WarningDigestsTable.tsx +298 -0
  61. package/templates/standard/dashboard/src/components/usage/react/UsageTable.tsx +385 -0
  62. package/templates/standard/dashboard/src/components/usage/unified/CircuitBreakerEvents.tsx +305 -0
  63. package/templates/standard/dashboard/src/components/usage/unified/FeatureBudgets.tsx +472 -0
  64. package/templates/standard/dashboard/src/lib/errors/api.ts +84 -0
  65. package/templates/standard/dashboard/src/lib/errors/types.ts +75 -0
  66. package/templates/standard/dashboard/src/lib/infrastructure/api.ts +141 -0
  67. package/templates/standard/dashboard/src/lib/infrastructure/gatus.ts.hbs +112 -0
  68. package/templates/standard/dashboard/src/lib/services/proxy/index.ts +20 -0
  69. package/templates/standard/dashboard/src/lib/services/proxy/proxy.ts +244 -0
  70. package/templates/standard/dashboard/src/lib/services/proxy/types.ts +81 -0
  71. package/templates/standard/tests/integration/platform-sentinel.test.ts +497 -0
  72. package/templates/standard/tests/unit/cloudflare/alerting.test.ts +480 -0
  73. package/templates/standard/tests/unit/error-collector/dedup.test.ts +350 -0
  74. package/templates/standard/tests/unit/error-collector/github.test.ts +187 -0
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Search API Client
3
+ *
4
+ * Functions for interacting with the platform-search worker.
5
+ * Used by dashboard components for platform-wide search.
6
+ *
7
+ * @module dashboard/lib/search/api
8
+ * @created 2026-02-03
9
+ * @task task-303.1
10
+ */
11
+
12
+ import type {
13
+ SearchResponse,
14
+ SearchStatsResponse,
15
+ IndexDocumentRequest,
16
+ BulkIndexRequest,
17
+ BulkIndexResponse,
18
+ SearchQueryParams,
19
+ SearchContentType,
20
+ ContentTypeConfig,
21
+ } from './types';
22
+
23
+ /** Base URL for search API (via service binding proxy) */
24
+ const API_BASE = '/api/search';
25
+
26
+ /**
27
+ * Perform a search query
28
+ */
29
+ export async function search(params: SearchQueryParams): Promise<SearchResponse> {
30
+ const searchParams = new URLSearchParams();
31
+ searchParams.set('q', params.q);
32
+ if (params.type) searchParams.set('type', params.type);
33
+ if (params.project) searchParams.set('project', params.project);
34
+ if (params.limit) searchParams.set('limit', String(params.limit));
35
+ if (params.offset) searchParams.set('offset', String(params.offset));
36
+
37
+ const url = `${API_BASE}?${searchParams.toString()}`;
38
+ const response = await fetch(url);
39
+
40
+ if (!response.ok) {
41
+ throw new Error(`Search failed: ${response.statusText}`);
42
+ }
43
+
44
+ return response.json();
45
+ }
46
+
47
+ /**
48
+ * Index a single document
49
+ */
50
+ export async function indexDocument(
51
+ doc: IndexDocumentRequest
52
+ ): Promise<{ success: boolean; id: string }> {
53
+ const response = await fetch(`${API_BASE}/index`, {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify(doc),
57
+ });
58
+
59
+ if (!response.ok) {
60
+ throw new Error(`Failed to index document: ${response.statusText}`);
61
+ }
62
+
63
+ return response.json();
64
+ }
65
+
66
+ /**
67
+ * Bulk index multiple documents
68
+ */
69
+ export async function bulkIndexDocuments(data: BulkIndexRequest): Promise<BulkIndexResponse> {
70
+ const response = await fetch(`${API_BASE}/index/bulk`, {
71
+ method: 'POST',
72
+ headers: { 'Content-Type': 'application/json' },
73
+ body: JSON.stringify(data),
74
+ });
75
+
76
+ if (!response.ok) {
77
+ throw new Error(`Bulk index failed: ${response.statusText}`);
78
+ }
79
+
80
+ return response.json();
81
+ }
82
+
83
+ /**
84
+ * Clear and reindex all documents of a content type
85
+ */
86
+ export async function reindexContentType(
87
+ contentType: SearchContentType
88
+ ): Promise<{ success: boolean; content_type: string; deleted: number; message: string }> {
89
+ const response = await fetch(`${API_BASE}/reindex/${contentType}`, {
90
+ method: 'POST',
91
+ });
92
+
93
+ if (!response.ok) {
94
+ throw new Error(`Reindex failed: ${response.statusText}`);
95
+ }
96
+
97
+ return response.json();
98
+ }
99
+
100
+ /**
101
+ * Delete a document from the index
102
+ */
103
+ export async function deleteDocument(id: string): Promise<{ success: boolean; deleted: string }> {
104
+ const response = await fetch(`${API_BASE}/index/${encodeURIComponent(id)}`, {
105
+ method: 'DELETE',
106
+ });
107
+
108
+ if (!response.ok) {
109
+ if (response.status === 404) {
110
+ throw new Error(`Document not found: ${id}`);
111
+ }
112
+ throw new Error(`Failed to delete document: ${response.statusText}`);
113
+ }
114
+
115
+ return response.json();
116
+ }
117
+
118
+ /**
119
+ * Get search index statistics
120
+ */
121
+ export async function getSearchStats(): Promise<SearchStatsResponse> {
122
+ const response = await fetch(`${API_BASE}/stats`);
123
+
124
+ if (!response.ok) {
125
+ throw new Error(`Failed to fetch search stats: ${response.statusText}`);
126
+ }
127
+
128
+ return response.json();
129
+ }
130
+
131
+ /** Content type configuration for UI display */
132
+ export const CONTENT_TYPE_CONFIG: Record<SearchContentType, ContentTypeConfig> = {
133
+ error: {
134
+ label: 'Errors',
135
+ icon: 'alert-circle',
136
+ color: 'text-red-500 dark:text-red-400',
137
+ },
138
+ pattern: {
139
+ label: 'Patterns',
140
+ icon: 'cpu',
141
+ color: 'text-purple-500 dark:text-purple-400',
142
+ },
143
+ setting: {
144
+ label: 'Settings',
145
+ icon: 'settings',
146
+ color: 'text-gray-500 dark:text-gray-400',
147
+ },
148
+ page: {
149
+ label: 'Pages',
150
+ icon: 'file-text',
151
+ color: 'text-blue-500 dark:text-blue-400',
152
+ },
153
+ service: {
154
+ label: 'Services',
155
+ icon: 'server',
156
+ color: 'text-green-500 dark:text-green-400',
157
+ },
158
+ opportunity: {
159
+ label: 'Opportunities',
160
+ icon: 'target',
161
+ color: 'text-orange-500 dark:text-orange-400',
162
+ },
163
+ draft: {
164
+ label: 'Drafts',
165
+ icon: 'edit',
166
+ color: 'text-cyan-500 dark:text-cyan-400',
167
+ },
168
+ project: {
169
+ label: 'Projects',
170
+ icon: 'folder',
171
+ color: 'text-indigo-500 dark:text-indigo-400',
172
+ },
173
+ };
174
+
175
+ /**
176
+ * Get content type configuration
177
+ */
178
+ export function getContentTypeConfig(contentType: SearchContentType): ContentTypeConfig {
179
+ return (
180
+ CONTENT_TYPE_CONFIG[contentType] || {
181
+ label: contentType,
182
+ icon: 'file',
183
+ color: 'text-gray-500',
184
+ }
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Highlight search terms in text
190
+ */
191
+ export function highlightSearchTerms(text: string, query: string): string {
192
+ if (!query.trim()) return text;
193
+
194
+ const terms = query
195
+ .toLowerCase()
196
+ .split(/\s+/)
197
+ .filter((t) => t.length > 2);
198
+
199
+ if (terms.length === 0) return text;
200
+
201
+ // Create regex pattern for all terms
202
+ const pattern = new RegExp(`(${terms.map(escapeRegex).join('|')})`, 'gi');
203
+
204
+ return text.replace(
205
+ pattern,
206
+ '<mark class="bg-yellow-200 dark:bg-yellow-800 rounded px-0.5">$1</mark>'
207
+ );
208
+ }
209
+
210
+ /**
211
+ * Escape special regex characters
212
+ */
213
+ function escapeRegex(str: string): string {
214
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
215
+ }
216
+
217
+ /**
218
+ * Store recent searches in localStorage
219
+ */
220
+ export function addRecentSearch(query: string): void {
221
+ if (typeof window === 'undefined') return;
222
+
223
+ const key = 'platform-search-recent';
224
+ const maxRecent = 10;
225
+
226
+ try {
227
+ const existing = JSON.parse(localStorage.getItem(key) || '[]') as string[];
228
+ const filtered = existing.filter((q) => q !== query);
229
+ const updated = [query, ...filtered].slice(0, maxRecent);
230
+ localStorage.setItem(key, JSON.stringify(updated));
231
+ } catch {
232
+ // Ignore localStorage errors
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Get recent searches from localStorage
238
+ */
239
+ export function getRecentSearches(): string[] {
240
+ if (typeof window === 'undefined') return [];
241
+
242
+ const key = 'platform-search-recent';
243
+
244
+ try {
245
+ return JSON.parse(localStorage.getItem(key) || '[]') as string[];
246
+ } catch {
247
+ return [];
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Clear recent searches
253
+ */
254
+ export function clearRecentSearches(): void {
255
+ if (typeof window === 'undefined') return;
256
+
257
+ localStorage.removeItem('platform-search-recent');
258
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Search Types
3
+ *
4
+ * TypeScript types for the platform search system.
5
+ *
6
+ * @module dashboard/lib/search/types
7
+ */
8
+
9
+ /** Valid content types for search */
10
+ export type SearchContentType =
11
+ | 'error'
12
+ | 'pattern'
13
+ | 'setting'
14
+ | 'page'
15
+ | 'service'
16
+ | 'project';
17
+
18
+ /** Project scope for search results */
19
+ export type SearchProject = 'platform' | '{{projectSlug}}' | null;
20
+
21
+ /** A search document in the index */
22
+ export interface SearchDocument {
23
+ id: string;
24
+ content_type: SearchContentType;
25
+ project: SearchProject;
26
+ title: string;
27
+ content: string;
28
+ url: string;
29
+ metadata: string | null;
30
+ indexed_at: number;
31
+ source_updated_at: number | null;
32
+ }
33
+
34
+ /** A search result with ranking and snippet */
35
+ export interface SearchResult extends SearchDocument {
36
+ rank: number;
37
+ snippet: string;
38
+ parsed_metadata?: Record<string, unknown>;
39
+ }
40
+
41
+ /** Search results grouped by content type */
42
+ export interface GroupedSearchResults {
43
+ [contentType: string]: SearchResult[];
44
+ }
45
+
46
+ /** Search response */
47
+ export interface SearchResponse {
48
+ results: SearchResult[];
49
+ grouped: GroupedSearchResults;
50
+ count: number;
51
+ query: string;
52
+ filters: {
53
+ type: SearchContentType | null;
54
+ project: string | null;
55
+ };
56
+ error?: string;
57
+ }
58
+
59
+ /** Request to index a document */
60
+ export interface IndexDocumentRequest {
61
+ id: string;
62
+ content_type: SearchContentType;
63
+ project?: SearchProject;
64
+ title: string;
65
+ content: string;
66
+ url: string;
67
+ metadata?: Record<string, unknown>;
68
+ source_updated_at?: number;
69
+ }
70
+
71
+ /** Bulk index request */
72
+ export interface BulkIndexRequest {
73
+ documents: IndexDocumentRequest[];
74
+ }
75
+
76
+ /** Bulk index response */
77
+ export interface BulkIndexResponse {
78
+ success: boolean;
79
+ indexed: number;
80
+ failed: number;
81
+ total: number;
82
+ }
83
+
84
+ /** Index statistics response */
85
+ export interface SearchStatsResponse {
86
+ total: number;
87
+ by_type: Array<{
88
+ content_type: string;
89
+ count: number;
90
+ }>;
91
+ by_project: Array<{
92
+ project: string;
93
+ count: number;
94
+ }>;
95
+ index_range: {
96
+ oldest: string | null;
97
+ newest: string | null;
98
+ };
99
+ }
100
+
101
+ /** Query parameters for search */
102
+ export interface SearchQueryParams {
103
+ q: string;
104
+ type?: SearchContentType;
105
+ project?: string;
106
+ limit?: number;
107
+ offset?: number;
108
+ }
109
+
110
+ /** Content type configuration for UI */
111
+ export interface ContentTypeConfig {
112
+ label: string;
113
+ icon: string;
114
+ color: string;
115
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Settings API Client
3
+ *
4
+ * Functions for interacting with the platform-settings worker.
5
+ * Used by dashboard components to read and update settings.
6
+ *
7
+ * @module dashboard/lib/settings/api
8
+ */
9
+
10
+ import type {
11
+ Setting,
12
+ ParsedSetting,
13
+ SettingsListResponse,
14
+ CategorySettingsResponse,
15
+ UpdateSettingRequest,
16
+ BulkUpdateRequest,
17
+ BulkUpdateResponse,
18
+ SettingsQueryParams,
19
+ SettingsProject,
20
+ SettingsCategory,
21
+ } from './types';
22
+
23
+ /** Base URL for settings API (via service binding proxy) */
24
+ const API_BASE = '/api/settings';
25
+
26
+ /**
27
+ * Fetch all settings with optional filters
28
+ */
29
+ export async function getSettings(params: SettingsQueryParams = {}): Promise<SettingsListResponse> {
30
+ const searchParams = new URLSearchParams();
31
+ if (params.project) searchParams.set('project', params.project);
32
+ if (params.category) searchParams.set('category', params.category);
33
+
34
+ const url = searchParams.toString() ? `${API_BASE}?${searchParams.toString()}` : API_BASE;
35
+ const response = await fetch(url);
36
+
37
+ if (!response.ok) {
38
+ throw new Error(`Failed to fetch settings: ${response.statusText}`);
39
+ }
40
+
41
+ return response.json();
42
+ }
43
+
44
+ /**
45
+ * Get settings for a specific project and category
46
+ */
47
+ export async function getCategorySettings(
48
+ project: SettingsProject,
49
+ category: SettingsCategory
50
+ ): Promise<CategorySettingsResponse> {
51
+ const response = await fetch(`${API_BASE}/${project}/${category}`);
52
+
53
+ if (!response.ok) {
54
+ throw new Error(`Failed to fetch category settings: ${response.statusText}`);
55
+ }
56
+
57
+ return response.json();
58
+ }
59
+
60
+ /**
61
+ * Get a single setting by project, category, and key
62
+ */
63
+ export async function getSetting(
64
+ project: SettingsProject,
65
+ category: SettingsCategory,
66
+ key: string
67
+ ): Promise<ParsedSetting> {
68
+ const response = await fetch(`${API_BASE}/${project}/${category}/${key}`);
69
+
70
+ if (!response.ok) {
71
+ if (response.status === 404) {
72
+ throw new Error(`Setting not found: ${project}/${category}/${key}`);
73
+ }
74
+ throw new Error(`Failed to fetch setting: ${response.statusText}`);
75
+ }
76
+
77
+ return response.json();
78
+ }
79
+
80
+ /**
81
+ * Update a setting
82
+ */
83
+ export async function updateSetting(
84
+ project: SettingsProject,
85
+ category: SettingsCategory,
86
+ key: string,
87
+ data: UpdateSettingRequest
88
+ ): Promise<Setting> {
89
+ const response = await fetch(`${API_BASE}/${project}/${category}/${key}`, {
90
+ method: 'PUT',
91
+ headers: { 'Content-Type': 'application/json' },
92
+ body: JSON.stringify(data),
93
+ });
94
+
95
+ if (!response.ok) {
96
+ throw new Error(`Failed to update setting: ${response.statusText}`);
97
+ }
98
+
99
+ return response.json();
100
+ }
101
+
102
+ /**
103
+ * Delete a setting
104
+ */
105
+ export async function deleteSetting(
106
+ project: SettingsProject,
107
+ category: SettingsCategory,
108
+ key: string
109
+ ): Promise<{ success: boolean; deleted: string }> {
110
+ const response = await fetch(`${API_BASE}/${project}/${category}/${key}`, {
111
+ method: 'DELETE',
112
+ });
113
+
114
+ if (!response.ok) {
115
+ if (response.status === 404) {
116
+ throw new Error(`Setting not found: ${project}/${category}/${key}`);
117
+ }
118
+ throw new Error(`Failed to delete setting: ${response.statusText}`);
119
+ }
120
+
121
+ return response.json();
122
+ }
123
+
124
+ /**
125
+ * Bulk update multiple settings
126
+ */
127
+ export async function bulkUpdateSettings(data: BulkUpdateRequest): Promise<BulkUpdateResponse> {
128
+ const response = await fetch(`${API_BASE}/bulk`, {
129
+ method: 'PUT',
130
+ headers: { 'Content-Type': 'application/json' },
131
+ body: JSON.stringify(data),
132
+ });
133
+
134
+ if (!response.ok) {
135
+ throw new Error(`Failed to bulk update settings: ${response.statusText}`);
136
+ }
137
+
138
+ return response.json();
139
+ }
140
+
141
+ /**
142
+ * Parse a JSON-encoded setting value with type safety
143
+ */
144
+ export function parseSettingValue<T>(setting: Setting, defaultValue: T): T {
145
+ try {
146
+ const parsed = JSON.parse(setting.value);
147
+ return parsed as T;
148
+ } catch {
149
+ return defaultValue;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Get project display name
155
+ */
156
+ export function getProjectDisplayName(project: SettingsProject): string {
157
+ switch (project) {
158
+ case 'global':
159
+ return 'Global';
160
+ case 'platform':
161
+ return 'Platform';
162
+ case '{{projectSlug}}':
163
+ return '{{projectName}}';
164
+ default:
165
+ return project;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Get category display name
171
+ */
172
+ export function getCategoryDisplayName(category: SettingsCategory): string {
173
+ switch (category) {
174
+ case 'notifications':
175
+ return 'Notifications';
176
+ case 'thresholds':
177
+ return 'Thresholds';
178
+ case 'display':
179
+ return 'Display';
180
+ case 'api':
181
+ return 'API';
182
+ case 'features':
183
+ return 'Features';
184
+ default:
185
+ return category;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Format updated timestamp for display
191
+ */
192
+ export function formatUpdatedAt(timestamp: number): string {
193
+ const date = new Date(timestamp * 1000);
194
+ return date.toLocaleDateString('en-AU', {
195
+ day: 'numeric',
196
+ month: 'short',
197
+ year: 'numeric',
198
+ hour: '2-digit',
199
+ minute: '2-digit',
200
+ });
201
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Settings Types
3
+ *
4
+ * TypeScript types for the platform settings system.
5
+ *
6
+ * @module dashboard/lib/settings/types
7
+ */
8
+
9
+ /** Valid project scopes for settings */
10
+ export type SettingsProject = 'global' | 'platform' | '{{projectSlug}}';
11
+
12
+ /** Valid setting categories */
13
+ export type SettingsCategory = 'notifications' | 'thresholds' | 'display' | 'api' | 'features';
14
+
15
+ /** A single setting */
16
+ export interface Setting {
17
+ id: string;
18
+ project: SettingsProject;
19
+ category: SettingsCategory;
20
+ key: string;
21
+ value: string; // JSON-encoded
22
+ description: string | null;
23
+ updated_at: number;
24
+ updated_by: string | null;
25
+ }
26
+
27
+ /** Setting with parsed value */
28
+ export interface ParsedSetting extends Setting {
29
+ parsed_value: unknown;
30
+ }
31
+
32
+ /** Settings grouped by project and category */
33
+ export interface GroupedSettings {
34
+ [project: string]: {
35
+ [category: string]: Setting[];
36
+ };
37
+ }
38
+
39
+ /** Settings list response */
40
+ export interface SettingsListResponse {
41
+ settings: Setting[];
42
+ grouped: GroupedSettings;
43
+ count: number;
44
+ }
45
+
46
+ /** Settings for a specific project/category */
47
+ export interface CategorySettingsResponse {
48
+ project: SettingsProject;
49
+ category: SettingsCategory;
50
+ settings: ParsedSetting[];
51
+ count: number;
52
+ }
53
+
54
+ /** Request to update a setting */
55
+ export interface UpdateSettingRequest {
56
+ value: unknown;
57
+ description?: string;
58
+ }
59
+
60
+ /** Bulk update request */
61
+ export interface BulkUpdateRequest {
62
+ settings: Array<{
63
+ project: SettingsProject;
64
+ category: SettingsCategory;
65
+ key: string;
66
+ value: unknown;
67
+ description?: string;
68
+ }>;
69
+ }
70
+
71
+ /** Bulk update response */
72
+ export interface BulkUpdateResponse {
73
+ success: boolean;
74
+ total: number;
75
+ succeeded: number;
76
+ failed: number;
77
+ results: Array<{
78
+ id: string;
79
+ success: boolean;
80
+ error?: string;
81
+ }>;
82
+ }
83
+
84
+ /** Query parameters for listing settings */
85
+ export interface SettingsQueryParams {
86
+ project?: SettingsProject;
87
+ category?: SettingsCategory;
88
+ }
89
+
90
+ /** Common global settings */
91
+ export interface GlobalNotificationSettings {
92
+ email_enabled: boolean;
93
+ slack_enabled: boolean;
94
+ digest_frequency: 'realtime' | 'hourly' | 'daily' | 'weekly';
95
+ }
96
+
97
+ export interface GlobalThresholdSettings {
98
+ warning_percent: number;
99
+ critical_percent: number;
100
+ }
101
+
102
+ export interface GlobalDisplaySettings {
103
+ dark_mode: 'system' | 'light' | 'dark';
104
+ }