created views for trackers in UI, added leaflet
This commit is contained in:
@@ -200,6 +200,9 @@ onBeforeUnmount(() => {
|
||||
<TabsTrigger value="devices">
|
||||
Devices
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="trackers">
|
||||
Trackers
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="users">
|
||||
<DataTableNoCheckbox :columns="user_columns" :data="user_data" :dropdownComponent="AdminUserDropdonw" />
|
||||
|
||||
58
management-ui/src/customcompometns/TrackerComponent.vue
Normal file
58
management-ui/src/customcompometns/TrackerComponent.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import TrackerDashboard from './TrackerDashboard.vue';
|
||||
import { nextTick, onMounted, ref, type PropType } from 'vue';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import L, {
|
||||
Map as LeafletMap,
|
||||
} from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
|
||||
const props = defineProps({
|
||||
guid: { type: String as PropType<string>, required: true },
|
||||
})
|
||||
const mapContainer = ref<HTMLDivElement | null>(null)
|
||||
onMounted(async () => {
|
||||
// wait until the flex layout has settled
|
||||
await nextTick()
|
||||
|
||||
if (!mapContainer.value) return
|
||||
|
||||
const map = L.map(mapContainer.value).setView([48.38, 31.17], 6)
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
}).addTo(map)
|
||||
|
||||
// force Leaflet to recalc its size
|
||||
map.invalidateSize()
|
||||
})
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<Tabs default-value="records">
|
||||
<TabsList class="space-x-8">
|
||||
<TabsTrigger value="dashboard">
|
||||
Dashboard
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="locations">
|
||||
Locations
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="dashboard">
|
||||
<TrackerDashboard />
|
||||
</TabsContent>
|
||||
<TabsContent value="locations">
|
||||
<div class="flex h-full">
|
||||
<Card class="w-1/2 p-4 flex flex-col">
|
||||
|
||||
</Card>
|
||||
<div class="w-1/2 p-4">
|
||||
<div ref="mapContainer" class="w-full h-full rounded-lg shadow-inner">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</template>
|
||||
77
management-ui/src/customcompometns/TrackerDashboard.vue
Normal file
77
management-ui/src/customcompometns/TrackerDashboard.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
NumberField,
|
||||
NumberFieldContent,
|
||||
NumberFieldDecrement,
|
||||
NumberFieldIncrement,
|
||||
NumberFieldInput,
|
||||
} from '@/components/ui/number-field'
|
||||
import AssignDevice from "./AssignDevice.vue";
|
||||
import { ref } from "vue";
|
||||
import Separator from "@/components/ui/separator/Separator.vue";
|
||||
import DataRangePicker from "./DataRangePicker.vue";
|
||||
const selectedUserIds = ref<string[]>([])
|
||||
const usrIDs = selectedUserIds.value
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card class="flex w-full max-w-4xl mx-auto">
|
||||
<CardHeader class="pb-4">
|
||||
<CardTitle>
|
||||
Dashboard
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-1 space-y-6">
|
||||
<div class="grid gap-5">
|
||||
<div class="grid space-y-2 grid-cols-4 items-center">
|
||||
<Label for="users">Allowed users</Label>
|
||||
<AssignDevice v-model="usrIDs" class="col-span-3 w-full"></AssignDevice>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-4 pt-2 gap-4">
|
||||
<Button>Start</Button>
|
||||
<Button>Stop</Button>
|
||||
</div>
|
||||
<Separator class="my-6">Communication settings</Separator>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<NumberField id="polling" :default-value="60" :min="30">
|
||||
<Label for="polling"> Timeout interwal in seconds </Label>
|
||||
<NumberFieldContent>
|
||||
<NumberFieldDecrement></NumberFieldDecrement>
|
||||
<NumberFieldInput></NumberFieldInput>
|
||||
<NumberFieldIncrement></NumberFieldIncrement>
|
||||
</NumberFieldContent>
|
||||
</NumberField>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<NumberField id="jitter" :default-value="10" :min="5">
|
||||
<Label for="jitter"> Jitter in seconds </Label>
|
||||
<NumberFieldContent>
|
||||
<NumberFieldDecrement></NumberFieldDecrement>
|
||||
<NumberFieldInput></NumberFieldInput>
|
||||
<NumberFieldIncrement></NumberFieldIncrement>
|
||||
</NumberFieldContent>
|
||||
</NumberField>
|
||||
</div>
|
||||
</div>
|
||||
<Separator></Separator>
|
||||
<div class="space-y-2 gap-4">
|
||||
<Label for="sleepdate">Date/time to sleep</Label>
|
||||
<DataRangePicker id="sleepdate"></DataRangePicker>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
@@ -39,6 +39,7 @@ const tracker_columns = [
|
||||
]
|
||||
|
||||
let treckerCtrl: AbortController | null = null
|
||||
|
||||
async function loadTrackers() {
|
||||
error.value = null
|
||||
loading.value = true
|
||||
@@ -68,11 +69,11 @@ onBeforeUnmount(() => treckerCtrl?.abort())
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div v-if="loading">Loading devices…</div>
|
||||
<div v-if="loading">Loading trackers</div>
|
||||
<div v-else-if="error" class="text-red-600">{{ error }}</div>
|
||||
<div v-else class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||
<router-link v-for="device in trackers_data" :key="device.guid"
|
||||
:to="{ name: 'DeviceView', params: { guid: device.guid } }" class="block group hover:no-underline">
|
||||
:to="{ name: 'TrackerView', params: { guid: device.guid } }" class="block group hover:no-underline">
|
||||
<Card class="h-full transition-shadow group-hover:shadow-lg">
|
||||
<CardHeader class="bg-primary/5">
|
||||
<CardTitle class="text-lg">
|
||||
|
||||
16
management-ui/src/pages/TrackerViev.vue
Normal file
16
management-ui/src/pages/TrackerViev.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
import Navbar from '@/customcompometns/Navbar.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { computed } from 'vue';
|
||||
import TrackerComponent from '@/customcompometns/TrackerComponent.vue';
|
||||
const mode = useColorMode()
|
||||
const route = useRoute()
|
||||
const guid = computed(() => String(route.params.guid ?? route.query.guid ?? ''))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Navbar>
|
||||
<TrackerComponent :guid="guid"></TrackerComponent>
|
||||
</Navbar>
|
||||
</template>
|
||||
@@ -8,6 +8,7 @@ import DeviceView from './pages/DeviceView.vue';
|
||||
import Forbidden from './pages/Forbidden.vue';
|
||||
import Create from './pages/Create.vue';
|
||||
import Trackers from '@/pages/Trackers.vue';
|
||||
import TrackerViev from '@/pages/TrackerViev.vue';
|
||||
import { auth } from './lib/auth';
|
||||
|
||||
|
||||
@@ -72,6 +73,13 @@ const routes = [
|
||||
component: Trackers,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/tracker/:guid', // ← new dynamic segment
|
||||
name: 'TrackerView',
|
||||
component: TrackerViev,
|
||||
props: true, // so `guid` shows up as a prop
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
Reference in New Issue
Block a user