een-api-toolkit 0.3.38 → 0.3.46
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/.claude/agents/docs-accuracy-reviewer.md +35 -5
- package/.claude/agents/een-auth-agent.md +28 -0
- package/.claude/agents/een-automations-agent.md +264 -0
- package/.claude/agents/een-devices-agent.md +5 -7
- package/.claude/agents/een-events-agent.md +40 -18
- package/.claude/agents/een-media-agent.md +12 -15
- package/.claude/agents/een-setup-agent.md +32 -0
- package/.claude/agents/een-users-agent.md +2 -2
- package/CHANGELOG.md +9 -75
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +815 -0
- package/dist/index.js +986 -719
- package/dist/index.js.map +1 -1
- package/docs/AI-CONTEXT.md +17 -1
- package/docs/ai-reference/AI-AUTH.md +42 -1
- package/docs/ai-reference/AI-AUTOMATIONS.md +833 -0
- package/docs/ai-reference/AI-DEVICES.md +1 -1
- package/docs/ai-reference/AI-EVENTS.md +2 -2
- package/docs/ai-reference/AI-GROUPING.md +128 -66
- package/docs/ai-reference/AI-MEDIA.md +1 -1
- package/docs/ai-reference/AI-SETUP.md +1 -1
- package/docs/ai-reference/AI-USERS.md +1 -1
- package/examples/vue-alerts-metrics/src/App.vue +7 -1
- package/examples/vue-automations/.env.example +11 -0
- package/examples/vue-automations/README.md +205 -0
- package/examples/vue-automations/e2e/app.spec.ts +83 -0
- package/examples/vue-automations/e2e/auth.spec.ts +468 -0
- package/examples/vue-automations/index.html +13 -0
- package/examples/vue-automations/package-lock.json +1722 -0
- package/examples/vue-automations/package.json +29 -0
- package/examples/vue-automations/playwright.config.ts +46 -0
- package/examples/vue-automations/src/App.vue +122 -0
- package/examples/vue-automations/src/main.ts +23 -0
- package/examples/vue-automations/src/router/index.ts +61 -0
- package/examples/vue-automations/src/views/Automations.vue +692 -0
- package/examples/vue-automations/src/views/Callback.vue +76 -0
- package/examples/vue-automations/src/views/Home.vue +172 -0
- package/examples/vue-automations/src/views/Login.vue +33 -0
- package/examples/vue-automations/src/views/Logout.vue +66 -0
- package/examples/vue-automations/src/vite-env.d.ts +1 -0
- package/examples/vue-automations/tsconfig.json +21 -0
- package/examples/vue-automations/tsconfig.node.json +10 -0
- package/examples/vue-automations/vite.config.ts +12 -0
- package/examples/vue-bridges/e2e/auth.spec.ts +97 -0
- package/examples/vue-bridges/src/App.vue +7 -1
- package/examples/vue-cameras/src/App.vue +7 -1
- package/examples/vue-event-subscriptions/src/App.vue +7 -1
- package/examples/vue-event-subscriptions/src/views/LiveEvents.vue +1 -1
- package/examples/vue-events/src/App.vue +7 -1
- package/examples/vue-layouts/src/App.vue +7 -1
- package/examples/vue-users/package-lock.json +2 -2
- package/examples/vue-users/package.json +1 -1
- package/examples/vue-users/src/App.vue +7 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Events, Alerts & Real-Time Streaming - EEN API Toolkit
|
|
2
2
|
|
|
3
|
-
> **Version:** 0.3.
|
|
3
|
+
> **Version:** 0.3.46
|
|
4
4
|
>
|
|
5
5
|
> Complete reference for events, alerts, metrics, and SSE subscriptions.
|
|
6
6
|
> Load this document when implementing event-driven features.
|
|
@@ -1632,7 +1632,7 @@ watch(selectedSubscriptionId, (newId) => {
|
|
|
1632
1632
|
<ul class="warning-list">
|
|
1633
1633
|
<li>SSE URLs are single-use. Once disconnected, the subscription cannot be reconnected.</li>
|
|
1634
1634
|
<li>To receive events again after disconnecting, create a new subscription.</li>
|
|
1635
|
-
<li>
|
|
1635
|
+
<li>SSE subscriptions have a server-determined 15-minute TTL (not configurable) and expire if not connected.</li>
|
|
1636
1636
|
</ul>
|
|
1637
1637
|
</div>
|
|
1638
1638
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Layouts API - EEN API Toolkit
|
|
2
2
|
|
|
3
|
-
> **Version:** 0.3.
|
|
3
|
+
> **Version:** 0.3.46
|
|
4
4
|
>
|
|
5
5
|
> Complete reference for layout management (camera grouping).
|
|
6
6
|
> Load this document when working with layouts.
|
|
@@ -241,16 +241,29 @@ if (error) {
|
|
|
241
241
|
|
|
242
242
|
---
|
|
243
243
|
|
|
244
|
-
## Vue
|
|
244
|
+
## Vue Component Example
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
```vue
|
|
249
|
-
<script setup lang="ts">
|
|
246
|
+
```typescript
|
|
250
247
|
import { ref, computed, onMounted } from 'vue'
|
|
251
|
-
import {
|
|
252
|
-
|
|
248
|
+
import {
|
|
249
|
+
getLayouts,
|
|
250
|
+
getCameras,
|
|
251
|
+
createLayout,
|
|
252
|
+
updateLayout,
|
|
253
|
+
deleteLayout,
|
|
254
|
+
type Layout,
|
|
255
|
+
type Camera,
|
|
256
|
+
type EenError,
|
|
257
|
+
type ListLayoutsParams,
|
|
258
|
+
type CreateLayoutParams,
|
|
259
|
+
type UpdateLayoutParams,
|
|
260
|
+
type LayoutSettings
|
|
261
|
+
} from 'een-api-toolkit'
|
|
262
|
+
import LayoutModal from '../components/LayoutModal.vue'
|
|
263
|
+
|
|
264
|
+
// Reactive state
|
|
253
265
|
const layouts = ref<Layout[]>([])
|
|
266
|
+
const cameras = ref<Camera[]>([])
|
|
254
267
|
const loading = ref(false)
|
|
255
268
|
const error = ref<EenError | null>(null)
|
|
256
269
|
const nextPageToken = ref<string | undefined>(undefined)
|
|
@@ -262,6 +275,20 @@ const params = ref<ListLayoutsParams>({
|
|
|
262
275
|
include: ['resourceCounts', 'effectivePermissions']
|
|
263
276
|
})
|
|
264
277
|
|
|
278
|
+
// Modal state
|
|
279
|
+
const showModal = ref(false)
|
|
280
|
+
const selectedLayout = ref<Layout | null>(null)
|
|
281
|
+
const modalLoading = ref(false)
|
|
282
|
+
const modalError = ref<string | null>(null)
|
|
283
|
+
|
|
284
|
+
// Default settings for new layouts
|
|
285
|
+
const defaultSettings: LayoutSettings = {
|
|
286
|
+
showCameraBorder: true,
|
|
287
|
+
showCameraName: true,
|
|
288
|
+
cameraAspectRatio: '16x9',
|
|
289
|
+
paneColumns: 3
|
|
290
|
+
}
|
|
291
|
+
|
|
265
292
|
async function fetchLayouts(fetchParams?: ListLayoutsParams, append = false) {
|
|
266
293
|
loading.value = true
|
|
267
294
|
error.value = null
|
|
@@ -271,7 +298,9 @@ async function fetchLayouts(fetchParams?: ListLayoutsParams, append = false) {
|
|
|
271
298
|
|
|
272
299
|
if (result.error) {
|
|
273
300
|
error.value = result.error
|
|
274
|
-
if (!append)
|
|
301
|
+
if (!append) {
|
|
302
|
+
layouts.value = []
|
|
303
|
+
}
|
|
275
304
|
nextPageToken.value = undefined
|
|
276
305
|
} else {
|
|
277
306
|
if (append) {
|
|
@@ -283,80 +312,113 @@ async function fetchLayouts(fetchParams?: ListLayoutsParams, append = false) {
|
|
|
283
312
|
}
|
|
284
313
|
|
|
285
314
|
loading.value = false
|
|
315
|
+
return result
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function fetchCameras() {
|
|
319
|
+
const result = await getCameras({ pageSize: 100, include: ['status'] })
|
|
320
|
+
if (result.data) {
|
|
321
|
+
cameras.value = result.data.results
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function refresh() {
|
|
326
|
+
return fetchLayouts()
|
|
286
327
|
}
|
|
287
328
|
|
|
288
329
|
async function fetchNextPage() {
|
|
289
330
|
if (!nextPageToken.value) return
|
|
290
|
-
|
|
331
|
+
// Destructure to explicitly exclude any existing pageToken from params
|
|
332
|
+
const { pageToken: _existingToken, ...restParams } = params.value
|
|
333
|
+
return fetchLayouts({ ...restParams, pageToken: nextPageToken.value }, true)
|
|
291
334
|
}
|
|
292
335
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
336
|
+
function openCreateModal() {
|
|
337
|
+
selectedLayout.value = null
|
|
338
|
+
modalError.value = null
|
|
339
|
+
showModal.value = true
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function openEditModal(layout: Layout) {
|
|
343
|
+
selectedLayout.value = layout
|
|
344
|
+
modalError.value = null
|
|
345
|
+
showModal.value = true
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function closeModal() {
|
|
349
|
+
showModal.value = false
|
|
350
|
+
selectedLayout.value = null
|
|
351
|
+
modalError.value = null
|
|
299
352
|
}
|
|
300
353
|
|
|
301
|
-
async function
|
|
302
|
-
|
|
303
|
-
|
|
354
|
+
async function handleSave(data: { name: string; settings: LayoutSettings; panes: Layout['panes'] }) {
|
|
355
|
+
modalLoading.value = true
|
|
356
|
+
modalError.value = null
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
if (selectedLayout.value) {
|
|
360
|
+
// Update existing layout
|
|
361
|
+
const updateParams: UpdateLayoutParams = {
|
|
362
|
+
name: data.name,
|
|
363
|
+
settings: data.settings,
|
|
364
|
+
panes: data.panes
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const result = await updateLayout(selectedLayout.value.id, updateParams)
|
|
368
|
+
if (result.error) {
|
|
369
|
+
modalError.value = result.error.message
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
// Create new layout
|
|
374
|
+
const createParams: CreateLayoutParams = {
|
|
375
|
+
name: data.name,
|
|
376
|
+
settings: data.settings,
|
|
377
|
+
panes: data.panes
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const result = await createLayout(createParams)
|
|
381
|
+
if (result.error) {
|
|
382
|
+
modalError.value = result.error.message
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
closeModal()
|
|
304
388
|
await fetchLayouts()
|
|
389
|
+
} catch (err) {
|
|
390
|
+
// Handle unexpected errors (network failures, state mutations, etc.)
|
|
391
|
+
modalError.value = err instanceof Error ? err.message : 'An unexpected error occurred'
|
|
392
|
+
console.error('handleSave error:', err)
|
|
393
|
+
} finally {
|
|
394
|
+
modalLoading.value = false
|
|
305
395
|
}
|
|
306
|
-
return result
|
|
307
396
|
}
|
|
308
397
|
|
|
309
398
|
async function handleDelete(layoutId: string) {
|
|
399
|
+
if (!confirm('Are you sure you want to delete this layout?')) {
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
modalLoading.value = true
|
|
404
|
+
modalError.value = null
|
|
405
|
+
|
|
310
406
|
const result = await deleteLayout(layoutId)
|
|
311
|
-
|
|
312
|
-
|
|
407
|
+
|
|
408
|
+
if (result.error) {
|
|
409
|
+
modalError.value = result.error.message
|
|
410
|
+
modalLoading.value = false
|
|
411
|
+
return
|
|
313
412
|
}
|
|
314
|
-
|
|
413
|
+
|
|
414
|
+
closeModal()
|
|
415
|
+
await fetchLayouts()
|
|
416
|
+
modalLoading.value = false
|
|
315
417
|
}
|
|
316
418
|
|
|
317
|
-
onMounted(() =>
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
<template>
|
|
321
|
-
<div class="layouts">
|
|
322
|
-
<div class="header">
|
|
323
|
-
<h2>Layouts</h2>
|
|
324
|
-
<button @click="fetchLayouts" :disabled="loading">
|
|
325
|
-
{{ loading ? 'Loading...' : 'Refresh' }}
|
|
326
|
-
</button>
|
|
327
|
-
</div>
|
|
328
|
-
|
|
329
|
-
<div v-if="loading && layouts.length === 0" class="loading">
|
|
330
|
-
Loading layouts...
|
|
331
|
-
</div>
|
|
332
|
-
|
|
333
|
-
<div v-else-if="error" class="error">
|
|
334
|
-
Error: {{ error.message }}
|
|
335
|
-
</div>
|
|
336
|
-
|
|
337
|
-
<div v-else>
|
|
338
|
-
<div v-if="layouts.length > 0" class="layout-grid">
|
|
339
|
-
<div v-for="layout in layouts" :key="layout.id" class="layout-card">
|
|
340
|
-
<h3>{{ layout.name }}</h3>
|
|
341
|
-
<p>Panes: {{ layout.panes.length }}</p>
|
|
342
|
-
<p>Columns: {{ layout.settings.paneColumns }}</p>
|
|
343
|
-
<div v-if="layout.effectivePermissions">
|
|
344
|
-
<span v-if="layout.effectivePermissions.edit">Can Edit</span>
|
|
345
|
-
<span v-if="layout.effectivePermissions.delete">Can Delete</span>
|
|
346
|
-
</div>
|
|
347
|
-
</div>
|
|
348
|
-
</div>
|
|
349
|
-
|
|
350
|
-
<p v-else>No layouts found.</p>
|
|
351
|
-
|
|
352
|
-
<div v-if="hasNextPage" class="pagination">
|
|
353
|
-
<button @click="fetchNextPage" :disabled="loading">
|
|
354
|
-
{{ loading ? 'Loading...' : 'Load More' }}
|
|
355
|
-
</button>
|
|
356
|
-
</div>
|
|
357
|
-
</div>
|
|
358
|
-
</div>
|
|
359
|
-
</template>
|
|
419
|
+
onMounted(async () => {
|
|
420
|
+
await Promise.all([fetchLayouts(), fetchCameras()])
|
|
421
|
+
})
|
|
360
422
|
```
|
|
361
423
|
|
|
362
424
|
---
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { onMounted, computed } from 'vue'
|
|
2
3
|
import { useAuthStore } from 'een-api-toolkit'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
const authStore = useAuthStore()
|
|
6
6
|
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
7
|
+
|
|
8
|
+
// Initialize auth store from storage on app mount
|
|
9
|
+
// This restores the session if a valid token exists in localStorage/sessionStorage
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
authStore.initialize()
|
|
12
|
+
})
|
|
7
13
|
</script>
|
|
8
14
|
|
|
9
15
|
<template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# EEN OAuth proxy URL
|
|
2
|
+
VITE_PROXY_URL=http://localhost:8787
|
|
3
|
+
|
|
4
|
+
# EEN OAuth client ID
|
|
5
|
+
VITE_EEN_CLIENT_ID=your-client-id-here
|
|
6
|
+
|
|
7
|
+
# OAuth redirect URI (must be exactly this for EEN IDP)
|
|
8
|
+
VITE_REDIRECT_URI=http://127.0.0.1:3333
|
|
9
|
+
|
|
10
|
+
# Enable debug logging
|
|
11
|
+
VITE_DEBUG=true
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# EEN API Toolkit - Vue Automations Example
|
|
2
|
+
|
|
3
|
+
A complete example showing how to use the een-api-toolkit automation functions in a Vue 3 application.
|
|
4
|
+
|
|
5
|
+
## Storage Strategy: Memory
|
|
6
|
+
|
|
7
|
+
This example uses the `memory` storage strategy for maximum security. This means:
|
|
8
|
+
|
|
9
|
+
- **Tokens are never written to disk** - immune to localStorage/sessionStorage XSS attacks
|
|
10
|
+
- **Page refresh requires re-authentication** - tokens exist only in memory
|
|
11
|
+
- **Each tab is independent** - opening a new tab requires separate login
|
|
12
|
+
|
|
13
|
+
This is the recommended strategy for high-security deployments where protecting against XSS token theft is critical.
|
|
14
|
+
|
|
15
|
+
## Features Demonstrated
|
|
16
|
+
|
|
17
|
+
- OAuth authentication flow (login, callback, logout)
|
|
18
|
+
- Protected routes with navigation guards
|
|
19
|
+
- List event alert condition rules
|
|
20
|
+
- List alert condition rules with actions and insights
|
|
21
|
+
- List alert action rules
|
|
22
|
+
- List alert actions (notifications, webhooks, etc.)
|
|
23
|
+
- Filter by enabled status
|
|
24
|
+
- Pagination support
|
|
25
|
+
|
|
26
|
+
## APIs Used
|
|
27
|
+
|
|
28
|
+
- `listEventAlertConditionRules()` - List event alert condition rules
|
|
29
|
+
- `listAlertConditionRules()` - List alert condition rules with optional includes
|
|
30
|
+
- `listAlertActionRules()` - List alert action rules
|
|
31
|
+
- `listAlertActions()` - List alert actions
|
|
32
|
+
- `useAuthStore()` - Authentication state management
|
|
33
|
+
- `getAuthUrl()` - Generate OAuth login URL
|
|
34
|
+
- `handleAuthCallback()` - Process OAuth callback
|
|
35
|
+
- `initEenToolkit()` - Toolkit initialization
|
|
36
|
+
|
|
37
|
+
## Setup
|
|
38
|
+
|
|
39
|
+
### Prerequisites
|
|
40
|
+
|
|
41
|
+
1. **Start the OAuth proxy** (required for authentication):
|
|
42
|
+
|
|
43
|
+
The OAuth proxy is a separate project that handles token management securely.
|
|
44
|
+
Clone and run it from: https://github.com/klaushofrichter/een-oauth-proxy
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# In a separate terminal, from the een-oauth-proxy directory
|
|
48
|
+
npm install
|
|
49
|
+
npm run dev
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The proxy should be running at `http://localhost:8787`.
|
|
53
|
+
|
|
54
|
+
### Example Setup
|
|
55
|
+
|
|
56
|
+
All commands below should be run from this example directory (`examples/vue-automations/`):
|
|
57
|
+
|
|
58
|
+
2. Copy the environment file:
|
|
59
|
+
```bash
|
|
60
|
+
# From examples/vue-automations/
|
|
61
|
+
cp .env.example .env
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
3. Edit `.env` with your EEN credentials:
|
|
65
|
+
```env
|
|
66
|
+
VITE_EEN_CLIENT_ID=your-client-id
|
|
67
|
+
VITE_PROXY_URL=http://localhost:8787
|
|
68
|
+
# DO NOT change the redirect URI - EEN IDP only permits this URL
|
|
69
|
+
VITE_REDIRECT_URI=http://127.0.0.1:3333
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
4. Install dependencies and start:
|
|
73
|
+
```bash
|
|
74
|
+
# From examples/vue-automations/
|
|
75
|
+
npm install
|
|
76
|
+
npm run dev
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
5. Open http://127.0.0.1:3333 in your browser.
|
|
80
|
+
|
|
81
|
+
**Important:** The EEN Identity Provider only permits `http://127.0.0.1:3333` as the OAuth redirect URI. Do not use `localhost` or other ports.
|
|
82
|
+
|
|
83
|
+
**Note:** Development and testing was done on macOS. The `npm run stop` command uses `lsof`, which is not available on Windows. Windows users should manually stop any process on port 3333 or use `npx kill-port 3333` instead.
|
|
84
|
+
|
|
85
|
+
## Project Structure
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
src/
|
|
89
|
+
├── main.ts # App entry, toolkit initialization
|
|
90
|
+
├── App.vue # Root component with navigation
|
|
91
|
+
├── router/
|
|
92
|
+
│ └── index.ts # Vue Router with auth guards
|
|
93
|
+
└── views/
|
|
94
|
+
├── Home.vue # Home page with user profile
|
|
95
|
+
├── Login.vue # OAuth login redirect
|
|
96
|
+
├── Callback.vue # OAuth callback handler
|
|
97
|
+
├── Automations.vue # Automation rules display
|
|
98
|
+
└── Logout.vue # Logout handler
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Key Code Examples
|
|
102
|
+
|
|
103
|
+
### Initializing the Toolkit (main.ts)
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { initEenToolkit } from 'een-api-toolkit'
|
|
107
|
+
|
|
108
|
+
initEenToolkit({
|
|
109
|
+
proxyUrl: import.meta.env.VITE_PROXY_URL,
|
|
110
|
+
clientId: import.meta.env.VITE_EEN_CLIENT_ID,
|
|
111
|
+
storageStrategy: 'memory', // Maximum security - tokens lost on refresh
|
|
112
|
+
debug: true
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Listing Event Alert Condition Rules
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { listEventAlertConditionRules, type EventAlertConditionRule } from 'een-api-toolkit'
|
|
120
|
+
|
|
121
|
+
const rules = ref<EventAlertConditionRule[]>([])
|
|
122
|
+
|
|
123
|
+
async function fetchRules() {
|
|
124
|
+
const result = await listEventAlertConditionRules({
|
|
125
|
+
pageSize: 10,
|
|
126
|
+
enabled: true // Only enabled rules
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (result.error) {
|
|
130
|
+
// Handle error
|
|
131
|
+
} else {
|
|
132
|
+
rules.value = result.data.results
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Listing Alert Condition Rules with Includes
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { listAlertConditionRules, type AlertConditionRule } from 'een-api-toolkit'
|
|
141
|
+
|
|
142
|
+
const rules = ref<AlertConditionRule[]>([])
|
|
143
|
+
|
|
144
|
+
async function fetchRules() {
|
|
145
|
+
const result = await listAlertConditionRules({
|
|
146
|
+
pageSize: 10,
|
|
147
|
+
include: ['actions', 'insights'] // Include related data
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
if (result.error) {
|
|
151
|
+
// Handle error
|
|
152
|
+
} else {
|
|
153
|
+
rules.value = result.data.results
|
|
154
|
+
// Each rule now has rule.actions and rule.insights populated
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Listing Alert Actions
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { listAlertActions, type AutomationAlertAction } from 'een-api-toolkit'
|
|
163
|
+
|
|
164
|
+
const actions = ref<AutomationAlertAction[]>([])
|
|
165
|
+
|
|
166
|
+
async function fetchActions() {
|
|
167
|
+
const result = await listAlertActions({
|
|
168
|
+
pageSize: 10,
|
|
169
|
+
type__in: ['notification', 'webhook', 'slack'] // Filter by action type
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
if (result.error) {
|
|
173
|
+
// Handle error
|
|
174
|
+
} else {
|
|
175
|
+
actions.value = result.data.results
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Auth Guard (router/index.ts)
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
router.beforeEach((to, from, next) => {
|
|
184
|
+
const authStore = useAuthStore()
|
|
185
|
+
|
|
186
|
+
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
|
|
187
|
+
next('/login')
|
|
188
|
+
} else {
|
|
189
|
+
next()
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Running E2E Tests
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Install Playwright browsers (first time only)
|
|
198
|
+
npx playwright install
|
|
199
|
+
|
|
200
|
+
# Run E2E tests
|
|
201
|
+
npm run test:e2e
|
|
202
|
+
|
|
203
|
+
# Run with UI
|
|
204
|
+
npm run test:e2e:ui
|
|
205
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
test.describe('Vue Automations Example - App', () => {
|
|
4
|
+
test.beforeEach(async ({ page }) => {
|
|
5
|
+
await page.goto('/')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('app loads with correct title', async ({ page }) => {
|
|
9
|
+
await expect(page).toHaveTitle(/EEN Automations/)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('header displays app name', async ({ page }) => {
|
|
13
|
+
await expect(page.locator('[data-testid="app-title"]')).toHaveText('EEN Automations Example')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('navigation shows Home and Login links when not authenticated', async ({ page }) => {
|
|
17
|
+
// Home link should be visible
|
|
18
|
+
await expect(page.locator('[data-testid="nav-home"]')).toBeVisible()
|
|
19
|
+
|
|
20
|
+
// Login link should be visible (not authenticated)
|
|
21
|
+
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
22
|
+
|
|
23
|
+
// Automations and Logout should NOT be visible (requires auth)
|
|
24
|
+
await expect(page.locator('[data-testid="nav-automations"]')).not.toBeVisible()
|
|
25
|
+
await expect(page.locator('[data-testid="nav-logout"]')).not.toBeVisible()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('home page shows not logged in message', async ({ page }) => {
|
|
29
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
|
|
30
|
+
await expect(page.locator('[data-testid="login-button"]')).toBeVisible()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('home page displays storage strategy', async ({ page }) => {
|
|
34
|
+
await expect(page.locator('[data-testid="storage-strategy"]')).toBeVisible()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('login page displays login button', async ({ page }) => {
|
|
38
|
+
await page.goto('/login')
|
|
39
|
+
|
|
40
|
+
await expect(page.locator('[data-testid="login-title"]')).toHaveText('Login')
|
|
41
|
+
await expect(page.locator('[data-testid="login-button"]')).toBeVisible()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('protected route redirects to login', async ({ page }) => {
|
|
45
|
+
await page.goto('/automations')
|
|
46
|
+
|
|
47
|
+
// Should be redirected to login page
|
|
48
|
+
await page.waitForURL('/login')
|
|
49
|
+
await expect(page).toHaveURL('/login')
|
|
50
|
+
await expect(page.locator('[data-testid="login-title"]')).toHaveText('Login')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('navigation between pages works', async ({ page }) => {
|
|
54
|
+
// Click Login link
|
|
55
|
+
await page.click('[data-testid="nav-login"]')
|
|
56
|
+
await page.waitForURL('/login')
|
|
57
|
+
await expect(page).toHaveURL('/login')
|
|
58
|
+
|
|
59
|
+
// Click Home link
|
|
60
|
+
await page.click('[data-testid="nav-home"]')
|
|
61
|
+
await page.waitForURL('/')
|
|
62
|
+
await expect(page).toHaveURL('/')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('home page shows features list', async ({ page }) => {
|
|
66
|
+
// Check that the features section is visible
|
|
67
|
+
const description = page.locator('.description')
|
|
68
|
+
await expect(description).toBeVisible()
|
|
69
|
+
|
|
70
|
+
// Check for features list items (6 features + 4 functions = 10 total)
|
|
71
|
+
await expect(description.locator('li')).toHaveCount(10)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('home page shows functions used', async ({ page }) => {
|
|
75
|
+
// Check that the functions used section mentions the automation functions
|
|
76
|
+
const description = page.locator('.description')
|
|
77
|
+
await expect(description.locator('code')).toHaveCount(4)
|
|
78
|
+
await expect(description.locator('text=listEventAlertConditionRules')).toBeVisible()
|
|
79
|
+
await expect(description.locator('text=listAlertConditionRules')).toBeVisible()
|
|
80
|
+
await expect(description.locator('text=listAlertActionRules')).toBeVisible()
|
|
81
|
+
await expect(description.locator('text=listAlertActions')).toBeVisible()
|
|
82
|
+
})
|
|
83
|
+
})
|