een-api-toolkit 0.0.18 → 0.1.4
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/CHANGELOG.md +8 -5
- package/README.md +29 -41
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +339 -821
- package/dist/index.js +295 -340
- package/dist/index.js.map +1 -1
- package/docs/AI-CONTEXT.md +151 -237
- package/examples/vue-bridges/.env.example +13 -0
- package/examples/vue-bridges/e2e/app.spec.ts +73 -0
- package/examples/vue-bridges/e2e/auth.spec.ts +206 -0
- package/examples/{vue-basic → vue-bridges}/index.html +1 -1
- package/examples/vue-bridges/package-lock.json +1583 -0
- package/examples/vue-bridges/package.json +28 -0
- package/examples/vue-bridges/playwright.config.ts +46 -0
- package/examples/vue-bridges/src/App.vue +108 -0
- package/examples/vue-bridges/src/router/index.ts +68 -0
- package/examples/vue-bridges/src/views/BridgeDetail.vue +279 -0
- package/examples/vue-bridges/src/views/Bridges.vue +297 -0
- package/examples/vue-bridges/src/views/Callback.vue +76 -0
- package/examples/vue-bridges/src/views/Home.vue +150 -0
- package/examples/vue-bridges/src/views/Login.vue +33 -0
- package/examples/vue-bridges/src/views/Logout.vue +66 -0
- package/examples/vue-bridges/src/vite-env.d.ts +12 -0
- package/examples/vue-cameras/e2e/app.spec.ts +2 -2
- package/examples/vue-cameras/e2e/auth.spec.ts +206 -0
- package/examples/vue-cameras/src/App.vue +4 -4
- package/examples/vue-cameras/src/views/CameraDetail.vue +57 -9
- package/examples/vue-cameras/src/views/Cameras.vue +69 -18
- package/examples/vue-cameras/src/views/Home.vue +36 -11
- package/examples/{vue-basic → vue-users}/README.md +4 -4
- package/examples/{vue-basic → vue-users}/e2e/app.spec.ts +3 -3
- package/examples/{vue-basic → vue-users}/e2e/auth.spec.ts +2 -2
- package/examples/vue-users/index.html +13 -0
- package/examples/{vue-basic → vue-users}/package-lock.json +3 -3
- package/examples/{vue-basic → vue-users}/package.json +1 -1
- package/examples/{vue-basic → vue-users}/src/App.vue +1 -1
- package/examples/vue-users/src/main.ts +23 -0
- package/examples/{vue-basic → vue-users}/src/views/Home.vue +27 -12
- package/examples/{vue-basic → vue-users}/src/views/Users.vue +51 -10
- package/examples/vue-users/tsconfig.json +21 -0
- package/examples/vue-users/tsconfig.node.json +10 -0
- package/examples/vue-users/vite.config.ts +12 -0
- package/package.json +1 -1
- /package/examples/{vue-basic → vue-bridges}/src/main.ts +0 -0
- /package/examples/{vue-basic → vue-bridges}/tsconfig.json +0 -0
- /package/examples/{vue-basic → vue-bridges}/tsconfig.node.json +0 -0
- /package/examples/{vue-basic → vue-bridges}/vite.config.ts +0 -0
- /package/examples/{vue-basic → vue-users}/.env.example +0 -0
- /package/examples/{vue-basic → vue-users}/playwright.config.ts +0 -0
- /package/examples/{vue-basic → vue-users}/src/router/index.ts +0 -0
- /package/examples/{vue-basic → vue-users}/src/views/Callback.vue +0 -0
- /package/examples/{vue-basic → vue-users}/src/views/Login.vue +0 -0
- /package/examples/{vue-basic → vue-users}/src/views/Logout.vue +0 -0
- /package/examples/{vue-basic → vue-users}/src/vite-env.d.ts +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, watch, onMounted } from 'vue'
|
|
3
|
+
import { getBridges, type Bridge, type BridgeStatus, type EenError, type ListBridgesParams } from 'een-api-toolkit'
|
|
4
|
+
|
|
5
|
+
// Status filter
|
|
6
|
+
const statusFilter = ref<BridgeStatus | ''>('')
|
|
7
|
+
|
|
8
|
+
// Reactive state
|
|
9
|
+
const bridges = ref<Bridge[]>([])
|
|
10
|
+
const loading = ref(false)
|
|
11
|
+
const error = ref<EenError | null>(null)
|
|
12
|
+
const nextPageToken = ref<string | undefined>(undefined)
|
|
13
|
+
const totalSize = ref<number | undefined>(undefined)
|
|
14
|
+
|
|
15
|
+
const hasNextPage = computed(() => !!nextPageToken.value)
|
|
16
|
+
|
|
17
|
+
const params = ref<ListBridgesParams>({
|
|
18
|
+
pageSize: 20,
|
|
19
|
+
include: ['deviceInfo', 'status', 'networkInfo']
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
async function fetchBridges(fetchParams?: ListBridgesParams, append = false) {
|
|
23
|
+
loading.value = true
|
|
24
|
+
error.value = null
|
|
25
|
+
|
|
26
|
+
const mergedParams = { ...params.value, ...fetchParams }
|
|
27
|
+
const result = await getBridges(mergedParams)
|
|
28
|
+
|
|
29
|
+
if (result.error) {
|
|
30
|
+
error.value = result.error
|
|
31
|
+
if (!append) {
|
|
32
|
+
bridges.value = []
|
|
33
|
+
totalSize.value = undefined
|
|
34
|
+
}
|
|
35
|
+
nextPageToken.value = undefined
|
|
36
|
+
} else {
|
|
37
|
+
if (append) {
|
|
38
|
+
bridges.value = [...bridges.value, ...result.data.results]
|
|
39
|
+
} else {
|
|
40
|
+
bridges.value = result.data.results
|
|
41
|
+
}
|
|
42
|
+
nextPageToken.value = result.data.nextPageToken
|
|
43
|
+
totalSize.value = result.data.totalSize
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
loading.value = false
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function refresh() {
|
|
51
|
+
return fetchBridges()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function fetchNextPage() {
|
|
55
|
+
if (!nextPageToken.value) return
|
|
56
|
+
return fetchBridges({ ...params.value, pageToken: nextPageToken.value }, true)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function setParams(newParams: ListBridgesParams) {
|
|
60
|
+
params.value = newParams
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Watch for status filter changes
|
|
64
|
+
watch(statusFilter, async (newStatus) => {
|
|
65
|
+
if (newStatus) {
|
|
66
|
+
setParams({
|
|
67
|
+
pageSize: 20,
|
|
68
|
+
include: ['deviceInfo', 'status', 'networkInfo'],
|
|
69
|
+
status__in: [newStatus]
|
|
70
|
+
})
|
|
71
|
+
} else {
|
|
72
|
+
setParams({
|
|
73
|
+
pageSize: 20,
|
|
74
|
+
include: ['deviceInfo', 'status', 'networkInfo']
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
await fetchBridges()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Helper to extract status string from the union type
|
|
81
|
+
function getStatusString(status?: BridgeStatus | { connectionStatus?: BridgeStatus }): BridgeStatus | undefined {
|
|
82
|
+
if (!status) return undefined
|
|
83
|
+
if (typeof status === 'string') return status
|
|
84
|
+
return status.connectionStatus
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Get status badge class
|
|
88
|
+
function getStatusClass(status?: BridgeStatus | { connectionStatus?: BridgeStatus }): string {
|
|
89
|
+
const statusStr = getStatusString(status)
|
|
90
|
+
switch (statusStr) {
|
|
91
|
+
case 'online':
|
|
92
|
+
return 'status-online'
|
|
93
|
+
case 'offline':
|
|
94
|
+
return 'status-offline'
|
|
95
|
+
case 'error':
|
|
96
|
+
return 'status-error'
|
|
97
|
+
default:
|
|
98
|
+
return 'status-unknown'
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
onMounted(() => {
|
|
103
|
+
fetchBridges()
|
|
104
|
+
})
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
<template>
|
|
108
|
+
<div class="bridges">
|
|
109
|
+
<div class="header">
|
|
110
|
+
<h2>Bridges</h2>
|
|
111
|
+
<div class="controls">
|
|
112
|
+
<select v-model="statusFilter" class="status-filter">
|
|
113
|
+
<option value="">All Statuses</option>
|
|
114
|
+
<option value="online">Online</option>
|
|
115
|
+
<option value="offline">Offline</option>
|
|
116
|
+
<option value="error">Error</option>
|
|
117
|
+
<option value="idle">Idle</option>
|
|
118
|
+
</select>
|
|
119
|
+
<button @click="refresh" :disabled="loading">
|
|
120
|
+
{{ loading ? 'Loading...' : 'Refresh' }}
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div v-if="totalSize !== undefined" class="total-count">
|
|
126
|
+
Total: {{ totalSize }} bridge{{ totalSize !== 1 ? 's' : '' }}
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div v-if="loading && bridges.length === 0" class="loading">
|
|
130
|
+
Loading bridges...
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div v-else-if="error" class="error">
|
|
134
|
+
Error: {{ error.message }}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div v-else>
|
|
138
|
+
<div v-if="bridges.length > 0" class="bridge-grid">
|
|
139
|
+
<router-link
|
|
140
|
+
v-for="bridge in bridges"
|
|
141
|
+
:key="bridge.id"
|
|
142
|
+
:to="`/bridges/${bridge.id}`"
|
|
143
|
+
class="bridge-card"
|
|
144
|
+
>
|
|
145
|
+
<div class="bridge-header">
|
|
146
|
+
<h3>{{ bridge.name }}</h3>
|
|
147
|
+
<span :class="['status-badge', getStatusClass(bridge.status)]">
|
|
148
|
+
{{ getStatusString(bridge.status) || 'Unknown' }}
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="bridge-details">
|
|
152
|
+
<p v-if="bridge.deviceInfo?.make || bridge.deviceInfo?.model">
|
|
153
|
+
<strong>Device:</strong>
|
|
154
|
+
{{ bridge.deviceInfo?.make || '' }} {{ bridge.deviceInfo?.model || '' }}
|
|
155
|
+
</p>
|
|
156
|
+
<p v-if="bridge.networkInfo?.localIpAddress">
|
|
157
|
+
<strong>IP:</strong> {{ bridge.networkInfo.localIpAddress }}
|
|
158
|
+
</p>
|
|
159
|
+
<p v-if="bridge.locationId">
|
|
160
|
+
<strong>Location:</strong> {{ bridge.locationId }}
|
|
161
|
+
</p>
|
|
162
|
+
<p v-if="bridge.tags && bridge.tags.length > 0">
|
|
163
|
+
<strong>Tags:</strong> {{ bridge.tags.join(', ') }}
|
|
164
|
+
</p>
|
|
165
|
+
</div>
|
|
166
|
+
</router-link>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<p v-else class="no-bridges">
|
|
170
|
+
No bridges found{{ statusFilter ? ' with selected filter' : '' }}.
|
|
171
|
+
</p>
|
|
172
|
+
|
|
173
|
+
<div v-if="hasNextPage" class="pagination">
|
|
174
|
+
<button @click="fetchNextPage" :disabled="loading">
|
|
175
|
+
{{ loading ? 'Loading...' : 'Load More' }}
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</template>
|
|
181
|
+
|
|
182
|
+
<style scoped>
|
|
183
|
+
.bridges {
|
|
184
|
+
max-width: 1000px;
|
|
185
|
+
margin: 0 auto;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.header {
|
|
189
|
+
display: flex;
|
|
190
|
+
justify-content: space-between;
|
|
191
|
+
align-items: center;
|
|
192
|
+
margin-bottom: 10px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.controls {
|
|
196
|
+
display: flex;
|
|
197
|
+
gap: 10px;
|
|
198
|
+
align-items: center;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.status-filter {
|
|
202
|
+
padding: 10px;
|
|
203
|
+
border: 1px solid #ddd;
|
|
204
|
+
border-radius: 4px;
|
|
205
|
+
font-size: 1rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.total-count {
|
|
209
|
+
color: #666;
|
|
210
|
+
margin-bottom: 20px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.bridge-grid {
|
|
214
|
+
display: grid;
|
|
215
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
216
|
+
gap: 20px;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.bridge-card {
|
|
220
|
+
background: #fff;
|
|
221
|
+
border: 1px solid #eee;
|
|
222
|
+
border-radius: 8px;
|
|
223
|
+
padding: 20px;
|
|
224
|
+
text-decoration: none;
|
|
225
|
+
color: inherit;
|
|
226
|
+
transition: box-shadow 0.2s, border-color 0.2s;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.bridge-card:hover {
|
|
230
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
231
|
+
border-color: #42b883;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.bridge-header {
|
|
235
|
+
display: flex;
|
|
236
|
+
justify-content: space-between;
|
|
237
|
+
align-items: flex-start;
|
|
238
|
+
margin-bottom: 15px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.bridge-header h3 {
|
|
242
|
+
margin: 0;
|
|
243
|
+
font-size: 1.1rem;
|
|
244
|
+
flex: 1;
|
|
245
|
+
margin-right: 10px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.status-badge {
|
|
249
|
+
padding: 4px 8px;
|
|
250
|
+
border-radius: 4px;
|
|
251
|
+
font-size: 0.75rem;
|
|
252
|
+
font-weight: 600;
|
|
253
|
+
text-transform: uppercase;
|
|
254
|
+
white-space: nowrap;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.status-online {
|
|
258
|
+
background: #d4edda;
|
|
259
|
+
color: #155724;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.status-offline {
|
|
263
|
+
background: #f8d7da;
|
|
264
|
+
color: #721c24;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.status-error {
|
|
268
|
+
background: #fff3cd;
|
|
269
|
+
color: #856404;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.status-unknown {
|
|
273
|
+
background: #e2e3e5;
|
|
274
|
+
color: #383d41;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.bridge-details p {
|
|
278
|
+
margin: 5px 0;
|
|
279
|
+
font-size: 0.9rem;
|
|
280
|
+
color: #666;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.bridge-details strong {
|
|
284
|
+
color: #333;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.no-bridges {
|
|
288
|
+
text-align: center;
|
|
289
|
+
color: #666;
|
|
290
|
+
padding: 40px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.pagination {
|
|
294
|
+
margin-top: 30px;
|
|
295
|
+
text-align: center;
|
|
296
|
+
}
|
|
297
|
+
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, ref } from 'vue'
|
|
3
|
+
import { useRouter } from 'vue-router'
|
|
4
|
+
import { handleAuthCallback } from 'een-api-toolkit'
|
|
5
|
+
|
|
6
|
+
const router = useRouter()
|
|
7
|
+
const error = ref<string | null>(null)
|
|
8
|
+
const processing = ref(true)
|
|
9
|
+
|
|
10
|
+
onMounted(async () => {
|
|
11
|
+
const url = new URL(window.location.href)
|
|
12
|
+
const code = url.searchParams.get('code')
|
|
13
|
+
const state = url.searchParams.get('state')
|
|
14
|
+
const errorParam = url.searchParams.get('error')
|
|
15
|
+
|
|
16
|
+
if (errorParam) {
|
|
17
|
+
error.value = `OAuth error: ${errorParam}`
|
|
18
|
+
processing.value = false
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!code || !state) {
|
|
23
|
+
error.value = 'Missing authorization code or state parameter'
|
|
24
|
+
processing.value = false
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await handleAuthCallback(code, state)
|
|
29
|
+
|
|
30
|
+
if (result.error) {
|
|
31
|
+
error.value = result.error.message
|
|
32
|
+
processing.value = false
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Success - redirect to bridges
|
|
37
|
+
router.push('/bridges')
|
|
38
|
+
})
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div class="callback">
|
|
43
|
+
<div v-if="processing" class="loading">
|
|
44
|
+
<h2>Authenticating...</h2>
|
|
45
|
+
<p>Please wait while we complete the login process.</p>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div v-else-if="error" class="error-state">
|
|
49
|
+
<h2>Authentication Failed</h2>
|
|
50
|
+
<p class="error">{{ error }}</p>
|
|
51
|
+
<router-link to="/login">
|
|
52
|
+
<button>Try Again</button>
|
|
53
|
+
</router-link>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<style scoped>
|
|
59
|
+
.callback {
|
|
60
|
+
text-align: center;
|
|
61
|
+
max-width: 400px;
|
|
62
|
+
margin: 0 auto;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
h2 {
|
|
66
|
+
margin-bottom: 20px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.loading p {
|
|
70
|
+
color: #666;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.error-state .error {
|
|
74
|
+
margin-bottom: 20px;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useAuthStore, getCurrentUser, type UserProfile, type EenError } from 'een-api-toolkit'
|
|
3
|
+
import { computed, ref, onMounted } from 'vue'
|
|
4
|
+
|
|
5
|
+
const authStore = useAuthStore()
|
|
6
|
+
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
|
7
|
+
|
|
8
|
+
// Reactive state for current user
|
|
9
|
+
const user = ref<UserProfile | null>(null)
|
|
10
|
+
const loading = ref(false)
|
|
11
|
+
const error = ref<EenError | null>(null)
|
|
12
|
+
|
|
13
|
+
async function fetchUser() {
|
|
14
|
+
if (!isAuthenticated.value) return
|
|
15
|
+
|
|
16
|
+
loading.value = true
|
|
17
|
+
error.value = null
|
|
18
|
+
|
|
19
|
+
const result = await getCurrentUser()
|
|
20
|
+
if (result.error) {
|
|
21
|
+
error.value = result.error
|
|
22
|
+
user.value = null
|
|
23
|
+
} else {
|
|
24
|
+
user.value = result.data
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
loading.value = false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onMounted(() => {
|
|
31
|
+
if (isAuthenticated.value) {
|
|
32
|
+
fetchUser()
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<div class="home">
|
|
39
|
+
<h2>Welcome to the EEN Bridges Example</h2>
|
|
40
|
+
|
|
41
|
+
<div v-if="isAuthenticated" data-testid="authenticated">
|
|
42
|
+
<div v-if="loading" class="loading">Loading user info...</div>
|
|
43
|
+
<div v-else-if="error" class="error">{{ error.message }}</div>
|
|
44
|
+
<div v-else-if="user" class="user-info" data-testid="user-info">
|
|
45
|
+
<p>Logged in as: <strong>{{ user.firstName }} {{ user.lastName }}</strong></p>
|
|
46
|
+
<p>Email: {{ user.email }}</p>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="actions">
|
|
50
|
+
<router-link to="/bridges">
|
|
51
|
+
<button data-testid="view-bridges-button">View Bridges</button>
|
|
52
|
+
</router-link>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div v-else class="login-prompt" data-testid="not-authenticated">
|
|
57
|
+
<p>Please log in to view your bridges.</p>
|
|
58
|
+
<router-link to="/login">
|
|
59
|
+
<button data-testid="login-button">Login</button>
|
|
60
|
+
</router-link>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="description">
|
|
64
|
+
<h3>About This Example</h3>
|
|
65
|
+
<p>
|
|
66
|
+
This example demonstrates how to use the <code>getBridges</code> and
|
|
67
|
+
<code>getBridge</code> functions from the EEN API Toolkit to display
|
|
68
|
+
and manage bridges from the Eagle Eye Networks platform.
|
|
69
|
+
</p>
|
|
70
|
+
<h4>Features</h4>
|
|
71
|
+
<ul>
|
|
72
|
+
<li>List bridges with pagination</li>
|
|
73
|
+
<li>Filter bridges by status</li>
|
|
74
|
+
<li>View bridge details</li>
|
|
75
|
+
<li>Display device and network information</li>
|
|
76
|
+
</ul>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<style scoped>
|
|
82
|
+
.home {
|
|
83
|
+
max-width: 600px;
|
|
84
|
+
margin: 0 auto;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
h2 {
|
|
88
|
+
margin-bottom: 20px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.user-info {
|
|
92
|
+
background: #f5f5f5;
|
|
93
|
+
padding: 15px;
|
|
94
|
+
border-radius: 4px;
|
|
95
|
+
margin-bottom: 20px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.user-info p {
|
|
99
|
+
margin: 5px 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.actions {
|
|
103
|
+
margin: 20px 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.login-prompt {
|
|
107
|
+
text-align: center;
|
|
108
|
+
padding: 20px;
|
|
109
|
+
background: #f5f5f5;
|
|
110
|
+
border-radius: 4px;
|
|
111
|
+
margin-bottom: 20px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.login-prompt p {
|
|
115
|
+
margin-bottom: 15px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.description {
|
|
119
|
+
margin-top: 40px;
|
|
120
|
+
padding-top: 20px;
|
|
121
|
+
border-top: 1px solid #eee;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.description h3 {
|
|
125
|
+
margin-bottom: 10px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.description h4 {
|
|
129
|
+
margin-top: 15px;
|
|
130
|
+
margin-bottom: 10px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.description p {
|
|
134
|
+
color: #666;
|
|
135
|
+
margin-bottom: 15px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.description ul {
|
|
139
|
+
list-style: disc;
|
|
140
|
+
padding-left: 20px;
|
|
141
|
+
color: #666;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.description code {
|
|
145
|
+
background: #f0f0f0;
|
|
146
|
+
padding: 2px 6px;
|
|
147
|
+
border-radius: 3px;
|
|
148
|
+
font-size: 0.9em;
|
|
149
|
+
}
|
|
150
|
+
</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
|
+
}
|
|
@@ -53,8 +53,8 @@ test.describe('Cameras Example App', () => {
|
|
|
53
53
|
|
|
54
54
|
// Check the about section
|
|
55
55
|
await expect(page.locator('.description h3')).toContainText('About This Example')
|
|
56
|
-
await expect(page.locator('.description')).toContainText('
|
|
57
|
-
await expect(page.locator('.description')).toContainText('
|
|
56
|
+
await expect(page.locator('.description')).toContainText('getCameras')
|
|
57
|
+
await expect(page.locator('.description')).toContainText('getCamera')
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
test('should have correct navigation structure', async ({ page }) => {
|