created views for trackers in UI, added leaflet
This commit is contained in:
25
management-ui/package-lock.json
generated
25
management-ui/package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"axios": "^1.11.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"lucide-vue-next": "^0.525.0",
|
||||
"reka-ui": "^2.5.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
@@ -28,6 +29,7 @@
|
||||
"devDependencies": {
|
||||
"@iconify-json/radix-icons": "^1.2.2",
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@types/leaflet": "^1.9.20",
|
||||
"@types/node": "^24.1.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
@@ -1273,6 +1275,23 @@
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/leaflet": {
|
||||
"version": "1.9.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz",
|
||||
"integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
|
||||
@@ -1996,6 +2015,12 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"axios": "^1.11.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"lucide-vue-next": "^0.525.0",
|
||||
"reka-ui": "^2.5.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
@@ -29,6 +30,7 @@
|
||||
"devDependencies": {
|
||||
"@iconify-json/radix-icons": "^1.2.2",
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@types/leaflet": "^1.9.20",
|
||||
"@types/node": "^24.1.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
|
||||
@@ -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