 Support filtering points by deviceId on the UI.

Also, display the broadcasting device when clicking on a point on the map.
This commit is contained in:
Fabio Manganiello 2025-03-26 22:10:29 +01:00
parent 70ba053016
commit 87f1da7688
Signed by: blacklight
GPG key ID: D90FBA7F76362774
5 changed files with 66 additions and 4 deletions
frontend/src

View file

@ -28,6 +28,7 @@
</div> </div>
<PointInfo :point="selectedPoint" <PointInfo :point="selectedPoint"
:device="devicesById[selectedPoint?.deviceId]"
ref="popup" ref="popup"
@remove="onRemove" @remove="onRemove"
@edit="editPoint" @edit="editPoint"
@ -36,6 +37,7 @@
<div class="controls"> <div class="controls">
<div class="form-container" v-if="showControls"> <div class="form-container" v-if="showControls">
<FilterForm :value="locationQuery" <FilterForm :value="locationQuery"
:devices="devices"
:disabled="loading" :disabled="loading"
:has-next-page="hasNextPage" :has-next-page="hasNextPage"
:has-prev-page="hasPrevPage" :has-prev-page="hasPrevPage"
@ -101,6 +103,7 @@ import Routes from '../mixins/Routes.vue';
import Timeline from './Timeline.vue'; import Timeline from './Timeline.vue';
import TimelineMetricsConfiguration from '../models/TimelineMetricsConfiguration'; import TimelineMetricsConfiguration from '../models/TimelineMetricsConfiguration';
import URLQueryHandler from '../mixins/URLQueryHandler.vue'; import URLQueryHandler from '../mixins/URLQueryHandler.vue';
import UserDevice from '../models/UserDevice';
useGeographic() useGeographic()
@ -125,6 +128,7 @@ export default {
data() { data() {
return { return {
devices: [] as UserDevice[],
loading: false, loading: false,
map: null as Optional<Map>, map: null as Optional<Map>,
mapView: null as Optional<View>, mapView: null as Optional<View>,
@ -143,6 +147,13 @@ export default {
}, },
computed: { computed: {
devicesById(): Record<string, string> {
return this.devices.reduce((acc: Record<string, string>, device: any) => {
acc[device.id] = device
return acc
}, {})
},
groupedGPSPoints(): GPSPoint[] { groupedGPSPoints(): GPSPoint[] {
// Reference refreshPoints to force reactivity // Reference refreshPoints to force reactivity
this.refreshPoints; this.refreshPoints;
@ -450,8 +461,12 @@ export default {
}, },
async mounted() { async mounted() {
this.initQuery() this.initQuery();
this.gpsPoints = await this.fetch() [this.gpsPoints, this.devices] = await Promise.all([
this.fetch(),
this.getMyDevices(),
])
this.map = this.createMap() this.map = this.createMap()
}, },
} }

View file

@ -50,6 +50,11 @@
</span> </span>
</p> </p>
<p class="device" v-if="device">
<font-awesome-icon icon="fas fa-mobile-alt" />
{{ device.name }}
</p>
<p class="locality" v-if="point.locality">{{ point.locality }}</p> <p class="locality" v-if="point.locality">{{ point.locality }}</p>
<p class="postal-code" v-if="point.postalCode">{{ point.postalCode }}</p> <p class="postal-code" v-if="point.postalCode">{{ point.postalCode }}</p>
<p class="country" v-if="country"> <p class="country" v-if="country">
@ -77,11 +82,15 @@ import { getCountryData, getEmojiFlag } from 'countries-list';
import Dates from '../mixins/Dates.vue'; import Dates from '../mixins/Dates.vue';
import GPSPoint from '../models/GPSPoint'; import GPSPoint from '../models/GPSPoint';
import UserDevice from '../models/UserDevice';
export default { export default {
emit: ['close', 'edit', 'remove'], emit: ['close', 'edit', 'remove'],
mixins: [Dates], mixins: [Dates],
props: { props: {
device: {
type: [UserDevice, null],
},
point: { point: {
type: [GPSPoint, null], type: [GPSPoint, null],
}, },
@ -271,6 +280,12 @@ export default {
} }
} }
.device {
font-size: 0.9em;
font-weight: bold;
letter-spacing: 0.05em;
}
.timestamp { .timestamp {
color: var(--color-heading); color: var(--color-heading);
font-weight: bold; font-weight: bold;

View file

@ -138,6 +138,17 @@
min="0" /> min="0" />
</div> </div>
<div class="device-container">
<label for="device">Device</label>
<select id="device"
name="device"
v-model="newFilter.deviceId"
:disabled="disabled">
<option value="">All</option>
<option v-for="device in devices" :key="device.id" :value="device.id">{{ device.name }}</option>
</select>
</div>
<div class="footer"> <div class="footer">
<button type="submit" :disabled="disabled || !changed"> <button type="submit" :disabled="disabled || !changed">
<font-awesome-icon icon="fas fa-check" />&nbsp;Apply <font-awesome-icon icon="fas fa-check" />&nbsp;Apply
@ -149,6 +160,8 @@
<script lang="ts"> <script lang="ts">
import _ from 'lodash' import _ from 'lodash'
import UserDevice from '../../models/UserDevice'
export default { export default {
emit: [ emit: [
'next-page', 'next-page',
@ -157,8 +170,13 @@ export default {
'refresh', 'refresh',
'set-resolution', 'set-resolution',
], ],
props: { props: {
value: Object, value: Object,
devices: {
type: Array as () => UserDevice[],
default: () => [],
},
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false, default: false,

View file

@ -10,7 +10,7 @@ export default {
await this.request(`/devices`) as { await this.request(`/devices`) as {
devices: UserDevice[] devices: UserDevice[]
} }
).devices; ).devices.map((device) => new UserDevice(device));
}, },
async registerDevice(name: string): Promise<UserDevice> { async registerDevice(name: string): Promise<UserDevice> {

View file

@ -1,6 +1,7 @@
class LocationQuery { class LocationQuery {
public limit: number = 250; public limit: number = 250;
public offset: number | null = null; public offset: number | null = null;
public deviceId: string | null = null;
public startDate: Date | null = null; public startDate: Date | null = null;
public endDate: Date | null = null; public endDate: Date | null = null;
public minId: number | null = null; public minId: number | null = null;
@ -10,9 +11,22 @@ class LocationQuery {
public postalCode: string | null = null; public postalCode: string | null = null;
public order: string = 'asc'; public order: string = 'asc';
constructor(public data: any) { constructor(data: {
limit?: number;
offset?: number;
deviceId?: string;
startDate?: Date;
endDate?: Date;
minId?: number;
maxId?: number;
country?: string;
locality?: string;
postalCode?: string;
order?: string;
}) {
this.limit = data.limit || this.limit; this.limit = data.limit || this.limit;
this.offset = data.offset || this.offset; this.offset = data.offset || this.offset;
this.deviceId = data.deviceId || this.deviceId;
this.startDate = data.startDate || this.startDate; this.startDate = data.startDate || this.startDate;
this.endDate = data.endDate || this.endDate; this.endDate = data.endDate || this.endDate;
this.minId = data.minId || this.minId; this.minId = data.minId || this.minId;