een-api-toolkit 0.0.13 → 0.0.18

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.
@@ -0,0 +1,249 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from 'vue'
3
+ import { useCameras, type CameraStatus } from 'een-api-toolkit'
4
+
5
+ // Status filter
6
+ const statusFilter = ref<CameraStatus | ''>('')
7
+
8
+ // Initial fetch with common includes
9
+ const {
10
+ cameras,
11
+ loading,
12
+ error,
13
+ hasNextPage,
14
+ totalSize,
15
+ fetchNextPage,
16
+ refresh,
17
+ setParams,
18
+ fetch
19
+ } = useCameras({
20
+ pageSize: 20,
21
+ include: ['deviceInfo', 'status']
22
+ })
23
+
24
+ // Watch for status filter changes
25
+ watch(statusFilter, async (newStatus) => {
26
+ if (newStatus) {
27
+ setParams({
28
+ pageSize: 20,
29
+ include: ['deviceInfo', 'status'],
30
+ status__in: [newStatus]
31
+ })
32
+ } else {
33
+ setParams({
34
+ pageSize: 20,
35
+ include: ['deviceInfo', 'status']
36
+ })
37
+ }
38
+ await fetch()
39
+ })
40
+
41
+ // Get status badge class
42
+ function getStatusClass(status?: CameraStatus): string {
43
+ switch (status) {
44
+ case 'online':
45
+ case 'streaming':
46
+ return 'status-online'
47
+ case 'offline':
48
+ case 'deviceOffline':
49
+ case 'bridgeOffline':
50
+ return 'status-offline'
51
+ case 'error':
52
+ case 'invalidCredentials':
53
+ return 'status-error'
54
+ default:
55
+ return 'status-unknown'
56
+ }
57
+ }
58
+ </script>
59
+
60
+ <template>
61
+ <div class="cameras">
62
+ <div class="header">
63
+ <h2>Cameras</h2>
64
+ <div class="controls">
65
+ <select v-model="statusFilter" class="status-filter">
66
+ <option value="">All Statuses</option>
67
+ <option value="online">Online</option>
68
+ <option value="streaming">Streaming</option>
69
+ <option value="offline">Offline</option>
70
+ <option value="deviceOffline">Device Offline</option>
71
+ <option value="bridgeOffline">Bridge Offline</option>
72
+ <option value="error">Error</option>
73
+ </select>
74
+ <button @click="refresh" :disabled="loading">
75
+ {{ loading ? 'Loading...' : 'Refresh' }}
76
+ </button>
77
+ </div>
78
+ </div>
79
+
80
+ <div v-if="totalSize !== undefined" class="total-count">
81
+ Total: {{ totalSize }} camera{{ totalSize !== 1 ? 's' : '' }}
82
+ </div>
83
+
84
+ <div v-if="loading && cameras.length === 0" class="loading">
85
+ Loading cameras...
86
+ </div>
87
+
88
+ <div v-else-if="error" class="error">
89
+ Error: {{ error.message }}
90
+ </div>
91
+
92
+ <div v-else>
93
+ <div v-if="cameras.length > 0" class="camera-grid">
94
+ <router-link
95
+ v-for="camera in cameras"
96
+ :key="camera.id"
97
+ :to="`/cameras/${camera.id}`"
98
+ class="camera-card"
99
+ >
100
+ <div class="camera-header">
101
+ <h3>{{ camera.name }}</h3>
102
+ <span :class="['status-badge', getStatusClass(camera.status)]">
103
+ {{ camera.status || 'Unknown' }}
104
+ </span>
105
+ </div>
106
+ <div class="camera-details">
107
+ <p v-if="camera.deviceInfo?.make || camera.deviceInfo?.model">
108
+ <strong>Device:</strong>
109
+ {{ camera.deviceInfo?.make || '' }} {{ camera.deviceInfo?.model || '' }}
110
+ </p>
111
+ <p v-if="camera.locationId">
112
+ <strong>Location:</strong> {{ camera.locationId }}
113
+ </p>
114
+ <p v-if="camera.tags && camera.tags.length > 0">
115
+ <strong>Tags:</strong> {{ camera.tags.join(', ') }}
116
+ </p>
117
+ </div>
118
+ </router-link>
119
+ </div>
120
+
121
+ <p v-else class="no-cameras">
122
+ No cameras found{{ statusFilter ? ' with selected filter' : '' }}.
123
+ </p>
124
+
125
+ <div v-if="hasNextPage" class="pagination">
126
+ <button @click="fetchNextPage" :disabled="loading">
127
+ {{ loading ? 'Loading...' : 'Load More' }}
128
+ </button>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </template>
133
+
134
+ <style scoped>
135
+ .cameras {
136
+ max-width: 1000px;
137
+ margin: 0 auto;
138
+ }
139
+
140
+ .header {
141
+ display: flex;
142
+ justify-content: space-between;
143
+ align-items: center;
144
+ margin-bottom: 10px;
145
+ }
146
+
147
+ .controls {
148
+ display: flex;
149
+ gap: 10px;
150
+ align-items: center;
151
+ }
152
+
153
+ .status-filter {
154
+ padding: 10px;
155
+ border: 1px solid #ddd;
156
+ border-radius: 4px;
157
+ font-size: 1rem;
158
+ }
159
+
160
+ .total-count {
161
+ color: #666;
162
+ margin-bottom: 20px;
163
+ }
164
+
165
+ .camera-grid {
166
+ display: grid;
167
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
168
+ gap: 20px;
169
+ }
170
+
171
+ .camera-card {
172
+ background: #fff;
173
+ border: 1px solid #eee;
174
+ border-radius: 8px;
175
+ padding: 20px;
176
+ text-decoration: none;
177
+ color: inherit;
178
+ transition: box-shadow 0.2s, border-color 0.2s;
179
+ }
180
+
181
+ .camera-card:hover {
182
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
183
+ border-color: #42b883;
184
+ }
185
+
186
+ .camera-header {
187
+ display: flex;
188
+ justify-content: space-between;
189
+ align-items: flex-start;
190
+ margin-bottom: 15px;
191
+ }
192
+
193
+ .camera-header h3 {
194
+ margin: 0;
195
+ font-size: 1.1rem;
196
+ flex: 1;
197
+ margin-right: 10px;
198
+ }
199
+
200
+ .status-badge {
201
+ padding: 4px 8px;
202
+ border-radius: 4px;
203
+ font-size: 0.75rem;
204
+ font-weight: 600;
205
+ text-transform: uppercase;
206
+ white-space: nowrap;
207
+ }
208
+
209
+ .status-online {
210
+ background: #d4edda;
211
+ color: #155724;
212
+ }
213
+
214
+ .status-offline {
215
+ background: #f8d7da;
216
+ color: #721c24;
217
+ }
218
+
219
+ .status-error {
220
+ background: #fff3cd;
221
+ color: #856404;
222
+ }
223
+
224
+ .status-unknown {
225
+ background: #e2e3e5;
226
+ color: #383d41;
227
+ }
228
+
229
+ .camera-details p {
230
+ margin: 5px 0;
231
+ font-size: 0.9rem;
232
+ color: #666;
233
+ }
234
+
235
+ .camera-details strong {
236
+ color: #333;
237
+ }
238
+
239
+ .no-cameras {
240
+ text-align: center;
241
+ color: #666;
242
+ padding: 40px;
243
+ }
244
+
245
+ .pagination {
246
+ margin-top: 30px;
247
+ text-align: center;
248
+ }
249
+ </style>
@@ -0,0 +1,125 @@
1
+ <script setup lang="ts">
2
+ import { useAuthStore, useCurrentUser } from 'een-api-toolkit'
3
+ import { computed } from 'vue'
4
+
5
+ const authStore = useAuthStore()
6
+ const isAuthenticated = computed(() => authStore.isAuthenticated)
7
+
8
+ // Fetch current user if authenticated
9
+ const { user, loading, error } = useCurrentUser({ immediate: true })
10
+ </script>
11
+
12
+ <template>
13
+ <div class="home">
14
+ <h2>Welcome to the EEN Cameras Example</h2>
15
+
16
+ <div v-if="isAuthenticated">
17
+ <div v-if="loading" class="loading">Loading user info...</div>
18
+ <div v-else-if="error" class="error">{{ error.message }}</div>
19
+ <div v-else-if="user" class="user-info">
20
+ <p>Logged in as: <strong>{{ user.firstName }} {{ user.lastName }}</strong></p>
21
+ <p>Email: {{ user.email }}</p>
22
+ </div>
23
+
24
+ <div class="actions">
25
+ <router-link to="/cameras">
26
+ <button>View Cameras</button>
27
+ </router-link>
28
+ </div>
29
+ </div>
30
+
31
+ <div v-else class="login-prompt">
32
+ <p>Please log in to view your cameras.</p>
33
+ <router-link to="/login">
34
+ <button>Login</button>
35
+ </router-link>
36
+ </div>
37
+
38
+ <div class="description">
39
+ <h3>About This Example</h3>
40
+ <p>
41
+ This example demonstrates how to use the <code>useCameras</code> and
42
+ <code>useCamera</code> composables from the EEN API Toolkit to display
43
+ and manage cameras from the Eagle Eye Networks platform.
44
+ </p>
45
+ <h4>Features</h4>
46
+ <ul>
47
+ <li>List cameras with pagination</li>
48
+ <li>Filter cameras by status</li>
49
+ <li>View camera details</li>
50
+ <li>Display device information</li>
51
+ </ul>
52
+ </div>
53
+ </div>
54
+ </template>
55
+
56
+ <style scoped>
57
+ .home {
58
+ max-width: 600px;
59
+ margin: 0 auto;
60
+ }
61
+
62
+ h2 {
63
+ margin-bottom: 20px;
64
+ }
65
+
66
+ .user-info {
67
+ background: #f5f5f5;
68
+ padding: 15px;
69
+ border-radius: 4px;
70
+ margin-bottom: 20px;
71
+ }
72
+
73
+ .user-info p {
74
+ margin: 5px 0;
75
+ }
76
+
77
+ .actions {
78
+ margin: 20px 0;
79
+ }
80
+
81
+ .login-prompt {
82
+ text-align: center;
83
+ padding: 20px;
84
+ background: #f5f5f5;
85
+ border-radius: 4px;
86
+ margin-bottom: 20px;
87
+ }
88
+
89
+ .login-prompt p {
90
+ margin-bottom: 15px;
91
+ }
92
+
93
+ .description {
94
+ margin-top: 40px;
95
+ padding-top: 20px;
96
+ border-top: 1px solid #eee;
97
+ }
98
+
99
+ .description h3 {
100
+ margin-bottom: 10px;
101
+ }
102
+
103
+ .description h4 {
104
+ margin-top: 15px;
105
+ margin-bottom: 10px;
106
+ }
107
+
108
+ .description p {
109
+ color: #666;
110
+ margin-bottom: 15px;
111
+ }
112
+
113
+ .description ul {
114
+ list-style: disc;
115
+ padding-left: 20px;
116
+ color: #666;
117
+ }
118
+
119
+ .description code {
120
+ background: #f0f0f0;
121
+ padding: 2px 6px;
122
+ border-radius: 3px;
123
+ font-size: 0.9em;
124
+ }
125
+ </style>
@@ -0,0 +1,33 @@
1
+ <script setup lang="ts">
2
+ import { getAuthUrl } from 'een-api-toolkit'
3
+
4
+ function login() {
5
+ // Redirect to EEN OAuth login
6
+ window.location.href = getAuthUrl()
7
+ }
8
+ </script>
9
+
10
+ <template>
11
+ <div class="login">
12
+ <h2>Login</h2>
13
+ <p>Click the button below to login with your Eagle Eye Networks account.</p>
14
+ <button @click="login">Login with Eagle Eye Networks</button>
15
+ </div>
16
+ </template>
17
+
18
+ <style scoped>
19
+ .login {
20
+ text-align: center;
21
+ max-width: 400px;
22
+ margin: 0 auto;
23
+ }
24
+
25
+ h2 {
26
+ margin-bottom: 20px;
27
+ }
28
+
29
+ p {
30
+ margin-bottom: 20px;
31
+ color: #666;
32
+ }
33
+ </style>
@@ -0,0 +1,66 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from 'vue'
3
+ import { useRouter } from 'vue-router'
4
+ import { revokeToken, useAuthStore } from 'een-api-toolkit'
5
+
6
+ const router = useRouter()
7
+ const authStore = useAuthStore()
8
+ const error = ref<string | null>(null)
9
+ const processing = ref(true)
10
+
11
+ onMounted(async () => {
12
+ if (!authStore.isAuthenticated) {
13
+ // Already logged out
14
+ router.push('/')
15
+ return
16
+ }
17
+
18
+ const result = await revokeToken()
19
+
20
+ if (result.error) {
21
+ error.value = result.error.message
22
+ processing.value = false
23
+ return
24
+ }
25
+
26
+ // Success - redirect to home
27
+ router.push('/')
28
+ })
29
+ </script>
30
+
31
+ <template>
32
+ <div class="logout">
33
+ <div v-if="processing" class="loading">
34
+ <h2>Logging out...</h2>
35
+ <p>Please wait while we complete the logout process.</p>
36
+ </div>
37
+
38
+ <div v-else-if="error" class="error-state">
39
+ <h2>Logout Failed</h2>
40
+ <p class="error">{{ error }}</p>
41
+ <router-link to="/">
42
+ <button>Return Home</button>
43
+ </router-link>
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <style scoped>
49
+ .logout {
50
+ text-align: center;
51
+ max-width: 400px;
52
+ margin: 0 auto;
53
+ }
54
+
55
+ h2 {
56
+ margin-bottom: 20px;
57
+ }
58
+
59
+ .loading p {
60
+ color: #666;
61
+ }
62
+
63
+ .error-state .error {
64
+ margin-bottom: 20px;
65
+ }
66
+ </style>
@@ -0,0 +1,12 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_PROXY_URL: string
5
+ readonly VITE_EEN_CLIENT_ID: string
6
+ readonly VITE_REDIRECT_URI?: string
7
+ readonly VITE_DEBUG?: string
8
+ }
9
+
10
+ interface ImportMeta {
11
+ readonly env: ImportMetaEnv
12
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "preserve",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true
18
+ },
19
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
20
+ "references": [{ "path": "./tsconfig.node.json" }]
21
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ server: {
7
+ // IMPORTANT: Must use 127.0.0.1:3333 for EEN OAuth callback
8
+ // The EEN Identity Provider only permits this specific redirect URI
9
+ host: '127.0.0.1',
10
+ port: 3333
11
+ }
12
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "een-api-toolkit",
3
- "version": "0.0.13",
3
+ "version": "0.0.18",
4
4
  "description": "EEN Video platform API v3.0 library for Vue 3",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",