diff --git a/management-ui/src/customcompometns/Admincomponent.vue b/management-ui/src/customcompometns/Admincomponent.vue index c75fd80..056734b 100644 --- a/management-ui/src/customcompometns/Admincomponent.vue +++ b/management-ui/src/customcompometns/Admincomponent.vue @@ -117,13 +117,71 @@ async function loadDevices() { } } +// ----- Trackers +type ApiTracker = { guid: string; name: string; users?: ApiDeviceUser[] } +type TrackersResponse = { trackers: ApiTracker[]; offset: number; limit: number; total: number } + +function isTrackersResponse(t: unknown): t is TrackersResponse { + const x = t as any + return !!x && Array.isArray(x.trackers) && + x.trackers.every((dev: any) => + dev && + typeof dev.guid === 'string' && + typeof dev.name === 'string' && + ( + dev.users === undefined || + ( + Array.isArray(dev.users) && + dev.users.every((u: any) => u && typeof u.username === 'string') + ) + ) + ) +} + +const trackers_data = ref([]) +const trackersLoading = ref(false) +const trackersError = ref(null) + +const tracker_columns = [ + { accessorKey: 'guid', header: 'GUID' }, + { accessorKey: 'devicename', header: 'Tracker' }, + { accessorKey: 'assigned_users', header: 'Users' }, +] + +let treckerCtrl: AbortController | null = null +async function loadTrackers() { + trackersError.value = null + trackersLoading.value = true + try { + treckerCtrl?.abort() + treckerCtrl = new AbortController() + const { data } = await api.get('/trackers', { signal: treckerCtrl.signal }) + + if (!isTrackersResponse(data)) throw new Error('Unexpected trackers response') + + // Transform API -> table shape + trackers_data.value = data.trackers.map((d) => ({ + guid: d.guid, + devicename: d.name, + assigned_users: Array.isArray(d.users) ? d.users.map(u => u.username).join(', ') : '', + })) + } catch (e: any) { + if (e?.name === 'CanceledError' || e?.message === 'canceled') return + trackersError.value = 'Failed to load trackers.' + } finally { + trackersLoading.value = false + } +} + onMounted(() => { loadUsers() loadDevices() + loadTrackers() }) onBeforeUnmount(() => { ctrl?.abort() devicesCtrl?.abort() + treckerCtrl?.abort() }) @@ -150,5 +208,9 @@ onBeforeUnmount(() => { + + + \ No newline at end of file diff --git a/management-ui/src/customcompometns/Navbar.vue b/management-ui/src/customcompometns/Navbar.vue index fd7141a..8ffb718 100644 --- a/management-ui/src/customcompometns/Navbar.vue +++ b/management-ui/src/customcompometns/Navbar.vue @@ -44,6 +44,13 @@ function navLinkClass(prefix: string) { > Devices + + Trackers + diff --git a/management-ui/src/customcompometns/TrackerGrid.vue b/management-ui/src/customcompometns/TrackerGrid.vue new file mode 100644 index 0000000..d79879f --- /dev/null +++ b/management-ui/src/customcompometns/TrackerGrid.vue @@ -0,0 +1,103 @@ + + + + + \ No newline at end of file diff --git a/management-ui/src/lib/interfaces.ts b/management-ui/src/lib/interfaces.ts index 4c1552b..89557d5 100644 --- a/management-ui/src/lib/interfaces.ts +++ b/management-ui/src/lib/interfaces.ts @@ -8,4 +8,4 @@ export interface Users { id: number, username: string, role: string -} \ No newline at end of file +} diff --git a/management-ui/src/pages/Create.vue b/management-ui/src/pages/Create.vue index cfdc099..2cf1944 100644 --- a/management-ui/src/pages/Create.vue +++ b/management-ui/src/pages/Create.vue @@ -27,6 +27,11 @@ type CreateUserPayload = { role: string // 'admin' | 'user' } +type CreateTrackerPayload = { + guid: string + name: string + userIds: number[] +} const router = useRouter() @@ -49,6 +54,11 @@ const user_form = reactive({ isAdmin: false, }) +const tracker_form = reactive({ + guid: uuidv4(), + name: '', +}) + // userIds from AssignDevice (expects v-model of string[] ids) const selectedUserIds = ref([]) @@ -65,6 +75,8 @@ watch( userError.value = null userSubmitting.value = false userSuccess.value = null + // tracker_form.guid = uuidv4() + // tracker_form.name = '' } } ) @@ -151,6 +163,35 @@ async function submitUser() { userSubmitting.value = false } } + +async function submitTracker() { + errorMsg.value = null + if (!canSubmit.value) { + errorMsg.value = 'Please provide a valid GUID and name.' + return + } + + const userIds: number[] = selectedUserIds.value + .map((s) => Number(s)) + .filter((n) => Number.isFinite(n) && n >= 0) + + const payload: CreateDevicePayload = { + guid: device_form.guid, + name: device_form.name.trim(), + userIds, + } + + submitting.value = true + try { + await api.post('/trackers/create', payload) + router.replace('/admin') + } catch (e: any) { + // keep client error generic + errorMsg.value = e?.response?.status === 403 ? 'Access denied.' : 'Failed to create device.' + } finally { + submitting.value = false + } +}