een-api-toolkit 0.3.43 → 0.3.47

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 (44) hide show
  1. package/.claude/agents/docs-accuracy-reviewer.md +35 -5
  2. package/.claude/agents/een-automations-agent.md +264 -0
  3. package/.claude/agents/een-devices-agent.md +5 -7
  4. package/.claude/agents/een-events-agent.md +30 -16
  5. package/.claude/agents/een-media-agent.md +12 -15
  6. package/.claude/agents/een-users-agent.md +2 -2
  7. package/CHANGELOG.md +6 -8
  8. package/dist/index.cjs +3 -3
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.ts +815 -0
  11. package/dist/index.js +986 -719
  12. package/dist/index.js.map +1 -1
  13. package/docs/AI-CONTEXT.md +17 -1
  14. package/docs/ai-reference/AI-AUTH.md +1 -1
  15. package/docs/ai-reference/AI-AUTOMATIONS.md +833 -0
  16. package/docs/ai-reference/AI-DEVICES.md +1 -1
  17. package/docs/ai-reference/AI-EVENTS.md +1 -1
  18. package/docs/ai-reference/AI-GROUPING.md +128 -66
  19. package/docs/ai-reference/AI-MEDIA.md +1 -1
  20. package/docs/ai-reference/AI-SETUP.md +1 -1
  21. package/docs/ai-reference/AI-USERS.md +1 -1
  22. package/examples/vue-automations/.env.example +11 -0
  23. package/examples/vue-automations/README.md +205 -0
  24. package/examples/vue-automations/e2e/app.spec.ts +83 -0
  25. package/examples/vue-automations/e2e/auth.spec.ts +468 -0
  26. package/examples/vue-automations/index.html +13 -0
  27. package/examples/vue-automations/package-lock.json +1722 -0
  28. package/examples/vue-automations/package.json +29 -0
  29. package/examples/vue-automations/playwright.config.ts +46 -0
  30. package/examples/vue-automations/src/App.vue +122 -0
  31. package/examples/vue-automations/src/main.ts +23 -0
  32. package/examples/vue-automations/src/router/index.ts +61 -0
  33. package/examples/vue-automations/src/views/Automations.vue +692 -0
  34. package/examples/vue-automations/src/views/Callback.vue +76 -0
  35. package/examples/vue-automations/src/views/Home.vue +172 -0
  36. package/examples/vue-automations/src/views/Login.vue +33 -0
  37. package/examples/vue-automations/src/views/Logout.vue +66 -0
  38. package/examples/vue-automations/src/vite-env.d.ts +1 -0
  39. package/examples/vue-automations/tsconfig.json +21 -0
  40. package/examples/vue-automations/tsconfig.node.json +10 -0
  41. package/examples/vue-automations/vite.config.ts +12 -0
  42. package/examples/vue-event-subscriptions/e2e/auth.spec.ts +8 -12
  43. package/package.json +1 -1
  44. package/scripts/setup-agents.ts +38 -19
@@ -0,0 +1,692 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, onMounted } from 'vue'
3
+ import {
4
+ listEventAlertConditionRules,
5
+ listAlertConditionRules,
6
+ listAlertActionRules,
7
+ listAlertActions,
8
+ type EventAlertConditionRule,
9
+ type AlertConditionRule,
10
+ type AlertActionRule,
11
+ type AutomationAlertAction,
12
+ type EenError
13
+ } from 'een-api-toolkit'
14
+
15
+ // Active tab
16
+ type TabName = 'eventAlertRules' | 'conditionRules' | 'actionRules' | 'actions'
17
+ const activeTab = ref<TabName>('eventAlertRules')
18
+
19
+ // Modal state
20
+ type SelectedItem = EventAlertConditionRule | AlertConditionRule | AlertActionRule | AutomationAlertAction
21
+ const showModal = ref(false)
22
+ const selectedItem = ref<SelectedItem | null>(null)
23
+ const selectedItemTitle = ref('')
24
+
25
+ function openModal(item: SelectedItem, title: string) {
26
+ selectedItem.value = item
27
+ selectedItemTitle.value = title
28
+ showModal.value = true
29
+ }
30
+
31
+ function closeModal() {
32
+ showModal.value = false
33
+ selectedItem.value = null
34
+ selectedItemTitle.value = ''
35
+ }
36
+
37
+ function formatJson(obj: unknown): string {
38
+ return JSON.stringify(obj, null, 2)
39
+ }
40
+
41
+ // Event Alert Condition Rules
42
+ const eventAlertRules = ref<EventAlertConditionRule[]>([])
43
+ const eventAlertRulesLoading = ref(false)
44
+ const eventAlertRulesError = ref<EenError | null>(null)
45
+ const eventAlertRulesNextToken = ref<string | undefined>(undefined)
46
+ const hasMoreEventAlertRules = computed(() => !!eventAlertRulesNextToken.value)
47
+
48
+ // Alert Condition Rules
49
+ const conditionRules = ref<AlertConditionRule[]>([])
50
+ const conditionRulesLoading = ref(false)
51
+ const conditionRulesError = ref<EenError | null>(null)
52
+ const conditionRulesNextToken = ref<string | undefined>(undefined)
53
+ const hasMoreConditionRules = computed(() => !!conditionRulesNextToken.value)
54
+
55
+ // Alert Action Rules
56
+ const actionRules = ref<AlertActionRule[]>([])
57
+ const actionRulesLoading = ref(false)
58
+ const actionRulesError = ref<EenError | null>(null)
59
+ const actionRulesNextToken = ref<string | undefined>(undefined)
60
+ const hasMoreActionRules = computed(() => !!actionRulesNextToken.value)
61
+
62
+ // Alert Actions
63
+ const actions = ref<AutomationAlertAction[]>([])
64
+ const actionsLoading = ref(false)
65
+ const actionsError = ref<EenError | null>(null)
66
+ const actionsNextToken = ref<string | undefined>(undefined)
67
+ const hasMoreActions = computed(() => !!actionsNextToken.value)
68
+
69
+ // Filter state
70
+ const enabledFilter = ref<'all' | 'enabled' | 'disabled'>('all')
71
+
72
+ // Fetch functions
73
+ async function fetchEventAlertRules(append = false) {
74
+ eventAlertRulesLoading.value = true
75
+ eventAlertRulesError.value = null
76
+
77
+ const params: Parameters<typeof listEventAlertConditionRules>[0] = {
78
+ pageSize: 10,
79
+ ...(append && eventAlertRulesNextToken.value ? { pageToken: eventAlertRulesNextToken.value } : {}),
80
+ ...(enabledFilter.value !== 'all' ? { enabled: enabledFilter.value === 'enabled' } : {})
81
+ }
82
+
83
+ const result = await listEventAlertConditionRules(params)
84
+
85
+ if (result.error) {
86
+ eventAlertRulesError.value = result.error
87
+ if (!append) eventAlertRules.value = []
88
+ eventAlertRulesNextToken.value = undefined
89
+ } else {
90
+ if (append) {
91
+ eventAlertRules.value = [...eventAlertRules.value, ...result.data.results]
92
+ } else {
93
+ eventAlertRules.value = result.data.results
94
+ }
95
+ eventAlertRulesNextToken.value = result.data.nextPageToken
96
+ }
97
+
98
+ eventAlertRulesLoading.value = false
99
+ }
100
+
101
+ async function fetchConditionRules(append = false) {
102
+ conditionRulesLoading.value = true
103
+ conditionRulesError.value = null
104
+
105
+ const params: Parameters<typeof listAlertConditionRules>[0] = {
106
+ pageSize: 10,
107
+ include: ['actions', 'insights'],
108
+ ...(append && conditionRulesNextToken.value ? { pageToken: conditionRulesNextToken.value } : {}),
109
+ ...(enabledFilter.value !== 'all' ? { enabled: enabledFilter.value === 'enabled' } : {})
110
+ }
111
+
112
+ const result = await listAlertConditionRules(params)
113
+
114
+ if (result.error) {
115
+ conditionRulesError.value = result.error
116
+ if (!append) conditionRules.value = []
117
+ conditionRulesNextToken.value = undefined
118
+ } else {
119
+ if (append) {
120
+ conditionRules.value = [...conditionRules.value, ...result.data.results]
121
+ } else {
122
+ conditionRules.value = result.data.results
123
+ }
124
+ conditionRulesNextToken.value = result.data.nextPageToken
125
+ }
126
+
127
+ conditionRulesLoading.value = false
128
+ }
129
+
130
+ async function fetchActionRules(append = false) {
131
+ actionRulesLoading.value = true
132
+ actionRulesError.value = null
133
+
134
+ const params: Parameters<typeof listAlertActionRules>[0] = {
135
+ pageSize: 10,
136
+ ...(append && actionRulesNextToken.value ? { pageToken: actionRulesNextToken.value } : {}),
137
+ ...(enabledFilter.value !== 'all' ? { enabled: enabledFilter.value === 'enabled' } : {})
138
+ }
139
+
140
+ const result = await listAlertActionRules(params)
141
+
142
+ if (result.error) {
143
+ actionRulesError.value = result.error
144
+ if (!append) actionRules.value = []
145
+ actionRulesNextToken.value = undefined
146
+ } else {
147
+ if (append) {
148
+ actionRules.value = [...actionRules.value, ...result.data.results]
149
+ } else {
150
+ actionRules.value = result.data.results
151
+ }
152
+ actionRulesNextToken.value = result.data.nextPageToken
153
+ }
154
+
155
+ actionRulesLoading.value = false
156
+ }
157
+
158
+ async function fetchActions(append = false) {
159
+ actionsLoading.value = true
160
+ actionsError.value = null
161
+
162
+ const params: Parameters<typeof listAlertActions>[0] = {
163
+ pageSize: 10,
164
+ ...(append && actionsNextToken.value ? { pageToken: actionsNextToken.value } : {}),
165
+ ...(enabledFilter.value !== 'all' ? { enabled: enabledFilter.value === 'enabled' } : {})
166
+ }
167
+
168
+ const result = await listAlertActions(params)
169
+
170
+ if (result.error) {
171
+ actionsError.value = result.error
172
+ if (!append) actions.value = []
173
+ actionsNextToken.value = undefined
174
+ } else {
175
+ if (append) {
176
+ actions.value = [...actions.value, ...result.data.results]
177
+ } else {
178
+ actions.value = result.data.results
179
+ }
180
+ actionsNextToken.value = result.data.nextPageToken
181
+ }
182
+
183
+ actionsLoading.value = false
184
+ }
185
+
186
+ function refreshCurrentTab() {
187
+ switch (activeTab.value) {
188
+ case 'eventAlertRules':
189
+ fetchEventAlertRules()
190
+ break
191
+ case 'conditionRules':
192
+ fetchConditionRules()
193
+ break
194
+ case 'actionRules':
195
+ fetchActionRules()
196
+ break
197
+ case 'actions':
198
+ fetchActions()
199
+ break
200
+ }
201
+ }
202
+
203
+ function loadMore() {
204
+ switch (activeTab.value) {
205
+ case 'eventAlertRules':
206
+ fetchEventAlertRules(true)
207
+ break
208
+ case 'conditionRules':
209
+ fetchConditionRules(true)
210
+ break
211
+ case 'actionRules':
212
+ fetchActionRules(true)
213
+ break
214
+ case 'actions':
215
+ fetchActions(true)
216
+ break
217
+ }
218
+ }
219
+
220
+ function switchTab(tab: TabName) {
221
+ activeTab.value = tab
222
+ // Fetch data for the tab if not loaded
223
+ switch (tab) {
224
+ case 'eventAlertRules':
225
+ if (eventAlertRules.value.length === 0 && !eventAlertRulesLoading.value) {
226
+ fetchEventAlertRules()
227
+ }
228
+ break
229
+ case 'conditionRules':
230
+ if (conditionRules.value.length === 0 && !conditionRulesLoading.value) {
231
+ fetchConditionRules()
232
+ }
233
+ break
234
+ case 'actionRules':
235
+ if (actionRules.value.length === 0 && !actionRulesLoading.value) {
236
+ fetchActionRules()
237
+ }
238
+ break
239
+ case 'actions':
240
+ if (actions.value.length === 0 && !actionsLoading.value) {
241
+ fetchActions()
242
+ }
243
+ break
244
+ }
245
+ }
246
+
247
+ const isLoading = computed(() => {
248
+ switch (activeTab.value) {
249
+ case 'eventAlertRules':
250
+ return eventAlertRulesLoading.value
251
+ case 'conditionRules':
252
+ return conditionRulesLoading.value
253
+ case 'actionRules':
254
+ return actionRulesLoading.value
255
+ case 'actions':
256
+ return actionsLoading.value
257
+ default:
258
+ return false
259
+ }
260
+ })
261
+
262
+ const hasMore = computed(() => {
263
+ switch (activeTab.value) {
264
+ case 'eventAlertRules':
265
+ return hasMoreEventAlertRules.value
266
+ case 'conditionRules':
267
+ return hasMoreConditionRules.value
268
+ case 'actionRules':
269
+ return hasMoreActionRules.value
270
+ case 'actions':
271
+ return hasMoreActions.value
272
+ default:
273
+ return false
274
+ }
275
+ })
276
+
277
+ onMounted(() => {
278
+ fetchEventAlertRules()
279
+ })
280
+ </script>
281
+
282
+ <template>
283
+ <div class="automations">
284
+ <div class="header">
285
+ <h2>Automations</h2>
286
+ <div class="controls">
287
+ <select v-model="enabledFilter" @change="refreshCurrentTab" data-testid="enabled-filter">
288
+ <option value="all">All</option>
289
+ <option value="enabled">Enabled Only</option>
290
+ <option value="disabled">Disabled Only</option>
291
+ </select>
292
+ <button @click="refreshCurrentTab" :disabled="isLoading" data-testid="refresh-button">
293
+ {{ isLoading ? 'Loading...' : 'Refresh' }}
294
+ </button>
295
+ </div>
296
+ </div>
297
+
298
+ <div class="tabs">
299
+ <button
300
+ :class="{ active: activeTab === 'eventAlertRules' }"
301
+ @click="switchTab('eventAlertRules')"
302
+ data-testid="tab-event-alert-rules"
303
+ >
304
+ Event Alert Rules
305
+ </button>
306
+ <button
307
+ :class="{ active: activeTab === 'conditionRules' }"
308
+ @click="switchTab('conditionRules')"
309
+ data-testid="tab-condition-rules"
310
+ >
311
+ Condition Rules
312
+ </button>
313
+ <button
314
+ :class="{ active: activeTab === 'actionRules' }"
315
+ @click="switchTab('actionRules')"
316
+ data-testid="tab-action-rules"
317
+ >
318
+ Action Rules
319
+ </button>
320
+ <button
321
+ :class="{ active: activeTab === 'actions' }"
322
+ @click="switchTab('actions')"
323
+ data-testid="tab-actions"
324
+ >
325
+ Actions
326
+ </button>
327
+ </div>
328
+
329
+ <!-- Event Alert Condition Rules -->
330
+ <div v-if="activeTab === 'eventAlertRules'" class="tab-content" data-testid="event-alert-rules-content">
331
+ <div v-if="eventAlertRulesLoading && eventAlertRules.length === 0" class="loading">
332
+ Loading event alert condition rules...
333
+ </div>
334
+ <div v-else-if="eventAlertRulesError" class="error">
335
+ Error: {{ eventAlertRulesError.message }}
336
+ </div>
337
+ <div v-else>
338
+ <table v-if="eventAlertRules.length > 0" data-testid="event-alert-rules-table">
339
+ <thead>
340
+ <tr>
341
+ <th>Name</th>
342
+ <th>Priority</th>
343
+ <th>Enabled</th>
344
+ <th>Output Types</th>
345
+ </tr>
346
+ </thead>
347
+ <tbody>
348
+ <tr
349
+ v-for="rule in eventAlertRules"
350
+ :key="rule.id"
351
+ class="clickable-row"
352
+ @click="openModal(rule, 'Event Alert Condition Rule')"
353
+ >
354
+ <td>{{ rule.name }}</td>
355
+ <td>{{ rule.priority }}</td>
356
+ <td>
357
+ <span :class="rule.enabled ? 'enabled' : 'disabled'">
358
+ {{ rule.enabled ? 'Yes' : 'No' }}
359
+ </span>
360
+ </td>
361
+ <td>{{ rule.outputAlertTypes?.join(', ') || '-' }}</td>
362
+ </tr>
363
+ </tbody>
364
+ </table>
365
+ <p v-else class="no-data">No event alert condition rules found.</p>
366
+ </div>
367
+ </div>
368
+
369
+ <!-- Alert Condition Rules -->
370
+ <div v-if="activeTab === 'conditionRules'" class="tab-content" data-testid="condition-rules-content">
371
+ <div v-if="conditionRulesLoading && conditionRules.length === 0" class="loading">
372
+ Loading alert condition rules...
373
+ </div>
374
+ <div v-else-if="conditionRulesError" class="error">
375
+ Error: {{ conditionRulesError.message }}
376
+ </div>
377
+ <div v-else>
378
+ <table v-if="conditionRules.length > 0" data-testid="condition-rules-table">
379
+ <thead>
380
+ <tr>
381
+ <th>Name</th>
382
+ <th>Type</th>
383
+ <th>Priority</th>
384
+ <th>Enabled</th>
385
+ <th>Actions</th>
386
+ </tr>
387
+ </thead>
388
+ <tbody>
389
+ <tr
390
+ v-for="rule in conditionRules"
391
+ :key="rule.id"
392
+ class="clickable-row"
393
+ @click="openModal(rule, 'Alert Condition Rule')"
394
+ >
395
+ <td>{{ rule.name }}</td>
396
+ <td>{{ rule.type }}</td>
397
+ <td>{{ rule.priority }}</td>
398
+ <td>
399
+ <span :class="rule.enabled ? 'enabled' : 'disabled'">
400
+ {{ rule.enabled ? 'Yes' : 'No' }}
401
+ </span>
402
+ </td>
403
+ <td>{{ rule.actions?.length ?? 0 }}</td>
404
+ </tr>
405
+ </tbody>
406
+ </table>
407
+ <p v-else class="no-data">No alert condition rules found.</p>
408
+ </div>
409
+ </div>
410
+
411
+ <!-- Alert Action Rules -->
412
+ <div v-if="activeTab === 'actionRules'" class="tab-content" data-testid="action-rules-content">
413
+ <div v-if="actionRulesLoading && actionRules.length === 0" class="loading">
414
+ Loading alert action rules...
415
+ </div>
416
+ <div v-else-if="actionRulesError" class="error">
417
+ Error: {{ actionRulesError.message }}
418
+ </div>
419
+ <div v-else>
420
+ <table v-if="actionRules.length > 0" data-testid="action-rules-table">
421
+ <thead>
422
+ <tr>
423
+ <th>Name</th>
424
+ <th>Enabled</th>
425
+ <th>Alert Types</th>
426
+ <th>Actions</th>
427
+ </tr>
428
+ </thead>
429
+ <tbody>
430
+ <tr
431
+ v-for="rule in actionRules"
432
+ :key="rule.id"
433
+ class="clickable-row"
434
+ @click="openModal(rule, 'Alert Action Rule')"
435
+ >
436
+ <td>{{ rule.name }}</td>
437
+ <td>
438
+ <span :class="rule.enabled ? 'enabled' : 'disabled'">
439
+ {{ rule.enabled ? 'Yes' : 'No' }}
440
+ </span>
441
+ </td>
442
+ <td>{{ rule.alertTypes?.join(', ') || '-' }}</td>
443
+ <td>{{ rule.alertActionIds?.length ?? 0 }}</td>
444
+ </tr>
445
+ </tbody>
446
+ </table>
447
+ <p v-else class="no-data">No alert action rules found.</p>
448
+ </div>
449
+ </div>
450
+
451
+ <!-- Alert Actions -->
452
+ <div v-if="activeTab === 'actions'" class="tab-content" data-testid="actions-content">
453
+ <div v-if="actionsLoading && actions.length === 0" class="loading">
454
+ Loading alert actions...
455
+ </div>
456
+ <div v-else-if="actionsError" class="error">
457
+ Error: {{ actionsError.message }}
458
+ </div>
459
+ <div v-else>
460
+ <table v-if="actions.length > 0" data-testid="actions-table">
461
+ <thead>
462
+ <tr>
463
+ <th>Name</th>
464
+ <th>Type</th>
465
+ <th>Enabled</th>
466
+ </tr>
467
+ </thead>
468
+ <tbody>
469
+ <tr
470
+ v-for="action in actions"
471
+ :key="action.id"
472
+ class="clickable-row"
473
+ @click="openModal(action, 'Alert Action')"
474
+ >
475
+ <td>{{ action.name }}</td>
476
+ <td>{{ action.type }}</td>
477
+ <td>
478
+ <span :class="action.enabled ? 'enabled' : 'disabled'">
479
+ {{ action.enabled ? 'Yes' : 'No' }}
480
+ </span>
481
+ </td>
482
+ </tr>
483
+ </tbody>
484
+ </table>
485
+ <p v-else class="no-data">No alert actions found.</p>
486
+ </div>
487
+ </div>
488
+
489
+ <!-- Pagination -->
490
+ <div v-if="hasMore" class="pagination">
491
+ <button @click="loadMore" :disabled="isLoading" data-testid="load-more-button">
492
+ {{ isLoading ? 'Loading...' : 'Load More' }}
493
+ </button>
494
+ </div>
495
+
496
+ <!-- Detail Modal -->
497
+ <div v-if="showModal" class="modal-overlay" @click.self="closeModal" data-testid="modal-overlay">
498
+ <div class="modal" data-testid="detail-modal">
499
+ <div class="modal-header">
500
+ <h3>{{ selectedItemTitle }}</h3>
501
+ <button class="modal-close" @click="closeModal" data-testid="modal-close">&times;</button>
502
+ </div>
503
+ <div class="modal-body">
504
+ <pre class="json-content" data-testid="modal-json">{{ formatJson(selectedItem) }}</pre>
505
+ </div>
506
+ </div>
507
+ </div>
508
+ </div>
509
+ </template>
510
+
511
+ <style scoped>
512
+ .automations {
513
+ max-width: 1000px;
514
+ margin: 0 auto;
515
+ }
516
+
517
+ .header {
518
+ display: flex;
519
+ justify-content: space-between;
520
+ align-items: center;
521
+ margin-bottom: 20px;
522
+ }
523
+
524
+ .controls {
525
+ display: flex;
526
+ gap: 10px;
527
+ align-items: center;
528
+ }
529
+
530
+ .controls select {
531
+ padding: 8px 12px;
532
+ border: 1px solid #ddd;
533
+ border-radius: 4px;
534
+ font-size: 1rem;
535
+ }
536
+
537
+ .tabs {
538
+ display: flex;
539
+ gap: 0;
540
+ margin-bottom: 20px;
541
+ border-bottom: 2px solid #eee;
542
+ }
543
+
544
+ .tabs button {
545
+ padding: 10px 20px;
546
+ background: none;
547
+ border: none;
548
+ border-bottom: 2px solid transparent;
549
+ margin-bottom: -2px;
550
+ cursor: pointer;
551
+ color: #666;
552
+ font-size: 0.95rem;
553
+ }
554
+
555
+ .tabs button:hover {
556
+ color: #333;
557
+ background: #f5f5f5;
558
+ }
559
+
560
+ .tabs button.active {
561
+ color: #4a90a4;
562
+ border-bottom-color: #4a90a4;
563
+ font-weight: 600;
564
+ }
565
+
566
+ .tab-content {
567
+ min-height: 200px;
568
+ }
569
+
570
+ table {
571
+ width: 100%;
572
+ border-collapse: collapse;
573
+ background: #fff;
574
+ }
575
+
576
+ th,
577
+ td {
578
+ padding: 12px;
579
+ text-align: left;
580
+ border-bottom: 1px solid #eee;
581
+ }
582
+
583
+ th {
584
+ background: #f5f5f5;
585
+ font-weight: 600;
586
+ }
587
+
588
+ .enabled {
589
+ color: #27ae60;
590
+ }
591
+
592
+ .disabled {
593
+ color: #e74c3c;
594
+ }
595
+
596
+ .no-data {
597
+ text-align: center;
598
+ color: #666;
599
+ padding: 40px;
600
+ }
601
+
602
+ .pagination {
603
+ margin-top: 20px;
604
+ text-align: center;
605
+ }
606
+
607
+ .loading {
608
+ text-align: center;
609
+ color: #666;
610
+ padding: 40px;
611
+ }
612
+
613
+ .clickable-row {
614
+ cursor: pointer;
615
+ transition: background-color 0.15s ease;
616
+ }
617
+
618
+ .clickable-row:hover {
619
+ background-color: #f0f7fa;
620
+ }
621
+
622
+ .modal-overlay {
623
+ position: fixed;
624
+ top: 0;
625
+ left: 0;
626
+ right: 0;
627
+ bottom: 0;
628
+ background: rgba(0, 0, 0, 0.5);
629
+ display: flex;
630
+ align-items: center;
631
+ justify-content: center;
632
+ z-index: 1000;
633
+ }
634
+
635
+ .modal {
636
+ background: #fff;
637
+ border-radius: 8px;
638
+ width: 80%;
639
+ max-height: 80vh;
640
+ display: flex;
641
+ flex-direction: column;
642
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
643
+ }
644
+
645
+ .modal-header {
646
+ display: flex;
647
+ justify-content: space-between;
648
+ align-items: center;
649
+ padding: 16px 20px;
650
+ border-bottom: 1px solid #eee;
651
+ }
652
+
653
+ .modal-header h3 {
654
+ margin: 0;
655
+ font-size: 1.1rem;
656
+ color: #333;
657
+ }
658
+
659
+ .modal-close {
660
+ background: none;
661
+ border: none;
662
+ font-size: 1.5rem;
663
+ cursor: pointer;
664
+ color: #666;
665
+ padding: 0;
666
+ line-height: 1;
667
+ }
668
+
669
+ .modal-close:hover {
670
+ color: #333;
671
+ }
672
+
673
+ .modal-body {
674
+ padding: 20px;
675
+ overflow: auto;
676
+ flex: 1;
677
+ }
678
+
679
+ .json-content {
680
+ background: #f8f9fa;
681
+ padding: 16px;
682
+ border-radius: 4px;
683
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
684
+ font-size: 0.85rem;
685
+ line-height: 1.5;
686
+ overflow-x: auto;
687
+ white-space: pre-wrap;
688
+ word-wrap: break-word;
689
+ margin: 0;
690
+ color: #333;
691
+ }
692
+ </style>