more cleanup

This commit is contained in:
2025-04-17 22:09:18 -04:00
parent 702da27468
commit 2a55bd056f
10 changed files with 116 additions and 137 deletions

View File

@ -1,8 +1,8 @@
<script setup>
import { selectedNodeLatestPowerMetric } from '@/store';
const props = defineProps({
channel: Number, // Channel number (1, 2, or 3)
channel: Number, // Channel number (1, 2, or 3),
latest: Array,
});
</script>
@ -10,13 +10,13 @@
<li class="flex p-3">
<div class="text-sm font-medium text-gray-900">Channel {{ channel }}</div>
<div class="ml-auto text-sm text-gray-700">
<span v-if="selectedNodeLatestPowerMetric">
<span v-if="selectedNodeLatestPowerMetric[`ch${channel}_voltage`]" >
{{ Number(selectedNodeLatestPowerMetric[`ch${channel}_voltage`]).toFixed(2) }}V
<span v-if="latest">
<span v-if="latest[`ch${channel}_voltage`]" >
{{ Number(latest[`ch${channel}_voltage`]).toFixed(2) }}V
</span>
<span v-else>???</span>
<span v-if="selectedNodeLatestPowerMetric[`ch${channel}_current`]">
/ {{ Number(selectedNodeLatestPowerMetric[`ch${channel}_current`]).toFixed(2) }}mA
<span v-if="latest[`ch${channel}_current`]">
/ {{ Number(latest[`ch${channel}_current`]).toFixed(2) }}mA
</span>
</span>
<span v-else>???</span>

View File

@ -20,16 +20,62 @@ import { copyShareLinkForNode, getTimeSpan, buildPath } from '@/utils';
import { useConfigStore } from '@/stores/configStore';
const configStore = useConfigStore();
const emit = defineEmits(['showPositionHistory']);
const emit = defineEmits(['showPositionHistory', 'showTraceRoute']);
const mapData = useMapStore();
function showPositionHistory(id) {
emit('showPositionHistory', id);
const mqttMetrics = ref([]);
const traceroutes = ref([]);
const deviceMetrics = ref([]);
const environmentMetrics = ref([]);
const powerMetrics = ref([]);
// Generalized function for loading data
async function loadData(path, params = {}, targetRef, metrics = false) {
try {
const response = await axios.get(buildPath(path), { params });
// Grab the first key from the response (assuming it's always an object with a single data array)
const key = Object.keys(response.data)[0];
const rawData = response.data[key] ?? [];
// Reverse if it's metric data, otherwise just assign
targetRef.value = metrics ? rawData.slice().reverse() : rawData;
} catch (error) {
console.error(`Error loading data from ${path}:`, error);
targetRef.value = [];
}
}
function showTraceRoute(traceroute) {
emit('showTraceRoute', traceroute);
state.selectedTraceRoute = traceroute;
// Utility function to calculate the timeFrom value based on a given time range
function calculateTimeFrom(timeRange) {
const time = getTimeSpan(timeRange).amount;
return new Date().getTime() - (time * 1000);
}
// Load MQTT metrics
function loadNodeMqttMetrics(nodeId) {
loadData(`/api/v1/nodes/${nodeId}/mqtt-metrics`, {}, mqttMetrics);
}
// Load Traceroutes
function loadNodeTraceroutes(nodeId) {
loadData(`/api/v1/nodes/${nodeId}/traceroutes`, { count: 5 }, traceroutes);
}
// Load Device Metrics
function loadNodeDeviceMetrics(nodeId) {
loadData(`/api/v1/nodes/${nodeId}/device-metrics`, { time_from: calculateTimeFrom(configStore.deviceMetricsTimeRange) }, deviceMetrics, true);
}
// Load Environment Metrics
function loadNodeEnvironmentMetrics(nodeId) {
loadData(`/api/v1/nodes/${nodeId}/environment-metrics`, { time_from: calculateTimeFrom(configStore.environmentMetricsTimeRange) }, environmentMetrics, true);
}
// Load Power Metrics
function loadNodePowerMetrics(nodeId) {
loadData(`/api/v1/nodes/${nodeId}/power-metrics`, { time_from: calculateTimeFrom(configStore.powerMetricsTimeRange) }, powerMetrics, true);
}
function loadNode(nodeId) {
@ -40,73 +86,6 @@ function loadNode(nodeId) {
loadNodePowerMetrics(nodeId);
}
function loadNodeMqttMetrics(nodeId) {
state.selectedNodeMqttMetrics = [];
axios.get(buildPath(`/api/v1/nodes/${nodeId}/mqtt-metrics`)).then((response) => {
state.selectedNodeMqttMetrics = response.data.mqtt_metrics;
}).catch(() => {
// do nothing
});
}
function loadNodeTraceroutes(nodeId) {
state.selectedNodeTraceroutes = [];
axios.get(buildPath(`/api/v1/nodes/${nodeId}/traceroutes`), {
params: {
count: 5,
},
}).then((response) => {
state.selectedNodeTraceroutes = response.data.traceroutes;
}).catch(() => {
// do nothing
});
}
function loadNodeDeviceMetrics(nodeId) {
const time = getTimeSpan(configStore.deviceMetricsTimeRange).amount
const timeFrom = new Date().getTime() - (time * 1000);
axios.get(buildPath(`/api/v1/nodes/${nodeId}/device-metrics`), {
params: {
time_from: timeFrom,
},
}).then((response) => {
// reverse response, as it's newest to oldest, but we want oldest to newest
state.selectedNodeDeviceMetrics = response.data.device_metrics.reverse();
}).catch(() => {
state.selectedNodeDeviceMetrics = [];
});
}
function loadNodeEnvironmentMetrics(nodeId) {
const time = getTimeSpan(configStore.environmentMetricsTimeRange).amount
const timeFrom = new Date().getTime() - (time * 1000);
axios.get(buildPath(`/api/v1/nodes/${nodeId}/environment-metrics`), {
params: {
time_from: timeFrom,
},
}).then((response) => {
// reverse response, as it's newest to oldest, but we want oldest to newest
state.selectedNodeEnvironmentMetrics = response.data.environment_metrics.reverse();
}).catch(() => {
state.selectedNodeEnvironmentMetrics = [];
});
}
function loadNodePowerMetrics(nodeId) {
const time = getTimeSpan(configStore.powerMetricsTimeRange).amount
const timeFrom = new Date().getTime() - (time * 1000);
axios.get(buildPath(`/api/v1/nodes/${nodeId}/power-metrics`), {
params: {
time_from: timeFrom,
},
}).then((response) => {
// reverse response, as it's newest to oldest, but we want oldest to newest
state.selectedNodePowerMetrics = response.data.power_metrics.reverse();
}).catch(() => {
state.selectedNodePowerMetrics = [];
});
}
watch(
() => state.selectedNode,
(newValue) => {
@ -263,17 +242,17 @@ watch(
<!-- lora config -->
<LoraConfig :node="state.selectedNode"/>
<!-- position -->
<Position @show-position-history="showPositionHistory" :node="state.selectedNode"/>
<Position @show-position-history="(nodeId) => $emit('showPositionHistory', nodeId)" :node="state.selectedNode"/>
<!-- device metrics -->
<DeviceMetricsChart :node="state.selectedNode"/>
<DeviceMetricsChart :node="state.selectedNode" :data="deviceMetrics"/>
<!-- environment metrics -->
<EnvironmentMetricsChart :node="state.selectedNode"/>
<EnvironmentMetricsChart :node="state.selectedNode" :data="environmentMetrics"/>
<!-- power metrics -->
<PowerMetricsChart/>
<PowerMetricsChart :data="powerMetrics"/>
<!-- mqtt -->
<MqttHistory />
<MqttHistory :data="mqttMetrics"/>
<!-- traceroutes -->
<Traceroutes @show-trace-route="showTraceRoute"/>
<Traceroutes @show-trace-route="(traceroute) => $emit('showTraceRoute', traceroute)" :data="traceroutes"/>
<!-- other -->
<OtherInfo :node="state.selectedNode"/>
<!-- share -->

View File

@ -1,12 +1,10 @@
<script setup>
const props = defineProps(['node']);
const props = defineProps(['node', 'data']);
import { computed } from 'vue';
import { state } from '@/store';
import MetricsChart from '@/components/Chart/Metrics.vue';
const chartData = computed(() => {
const metrics = state.selectedNodeDeviceMetrics;
const metrics = props.data;
return {
labels: metrics.map(m => m.created_at),

View File

@ -1,16 +1,14 @@
<script setup>
const props = defineProps(['node']);
const props = defineProps(['node', 'data']);
import { computed } from 'vue';
import { state } from '@/store';
import { formatTemperature } from '@/utils';
import MetricsChart from '@/components/Chart/Metrics.vue';
// Chart data prep
const labels = computed(() => state.selectedNodeEnvironmentMetrics.map(m => m.created_at));
const temperatureMetrics = computed(() => state.selectedNodeEnvironmentMetrics.map(m => m.temperature));
const relativeHumidityMetrics = computed(() => state.selectedNodeEnvironmentMetrics.map(m => m.relative_humidity));
const barometricPressureMetrics = computed(() => state.selectedNodeEnvironmentMetrics.map(m => m.barometric_pressure));
const labels = computed(() => props.data.map(m => m.created_at));
const temperatureMetrics = computed(() => props.data.map(m => m.temperature));
const relativeHumidityMetrics = computed(() => props.data.map(m => m.relative_humidity));
const barometricPressureMetrics = computed(() => props.data.map(m => m.barometric_pressure));
const chartData = computed(() => ({
labels: labels.value,
@ -109,7 +107,7 @@ const legendData = {
<li class="flex p-3">
<div class="text-sm font-medium text-gray-900">Temperature</div>
<div class="ml-auto text-sm text-gray-700">
<span v-if="state.selectedNode?.temperature">{{ formatTemperature(state.selectedNode.temperature) }}</span>
<span v-if="props.node?.temperature">{{ formatTemperature(props.node.temperature) }}</span>
<span v-else>???</span>
</div>
</li>
@ -117,8 +115,8 @@ const legendData = {
<li class="flex p-3">
<div class="text-sm font-medium text-gray-900">Relative Humidity</div>
<div class="ml-auto text-sm text-gray-700">
<span v-if="state.selectedNode?.relative_humidity">
{{ Number(state.selectedNode.relative_humidity).toFixed(0) }}%
<span v-if="props.node?.relative_humidity">
{{ Number(props.node.relative_humidity).toFixed(0) }}%
</span>
<span v-else>???</span>
</div>
@ -127,8 +125,8 @@ const legendData = {
<li class="flex p-3">
<div class="text-sm font-medium text-gray-900">Barometric Pressure</div>
<div class="ml-auto text-sm text-gray-700">
<span v-if="state.selectedNode?.barometric_pressure">
{{ Number(state.selectedNode.barometric_pressure).toFixed(1) }} hPa
<span v-if="props.node?.barometric_pressure">
{{ Number(props.node.barometric_pressure).toFixed(1) }} hPa
</span>
<span v-else>???</span>
</div>

View File

@ -1,9 +1,9 @@
<script setup>
import { state } from '@/store';
const props = defineProps(['data']);
import moment from 'moment';
import { computed } from 'vue';
const mqttMetrics = computed(() => state.selectedNodeMqttMetrics);
const mqttMetrics = computed(() => props.data);
</script>
<template>
<div>

View File

@ -1,8 +1,8 @@
<script setup>
import { computed } from 'vue';
import { state, selectedNodeLatestPowerMetric } from '@/store';
import MetricsChart from '@/components/Chart/Metrics.vue';
import ChannelData from '@/components/Chart/PowerMetrics/ChannelData.vue';
const props = defineProps(['data']);
const legendData = {
'Channel 1': 'bg-blue-500',
@ -10,9 +10,14 @@ const legendData = {
'Channel 3': 'bg-orange-500',
};
const latestMetric = computed(() => {
const [ latest ] = (props.data ?? []).slice(-1);
return latest;
});
// Computed dataset for the chart container
const chartData = computed(() => {
const metrics = state.selectedNodePowerMetrics;
const metrics = props.data;
const labels = metrics.map(m => m.created_at);
const colors = ['#3b82f6', '#22c55e', '#f97316'];
@ -109,6 +114,6 @@ const chartConfig = {
:legendData="legendData"
:chartConfig="chartConfig"
>
<ChannelData v-for="i in [1, 2, 3]" :key="i" :channel="i" />
<ChannelData v-for="i in [1, 2, 3]" :key="i" :channel="i" :latest="latestMetric"/>
</MetricsChart>
</template>

View File

@ -1,7 +1,7 @@
<script setup>
const props = defineProps(['data']);
const emit = defineEmits(['showTraceRoute']);
import moment from 'moment';
import { state } from '@/store';
import { useMapStore } from '@/stores/mapStore';
const mapData = useMapStore();
</script>
@ -12,8 +12,8 @@ const mapData = useMapStore();
<div class="text-sm text-gray-600">Only 5 most recent are shown</div>
</div>
<ul role="list" class="flex-1 divide-y divide-gray-200">
<template v-if="state.selectedNodeTraceroutes.length > 0">
<li @click="$emit('showTraceRoute', traceroute)" v-for="traceroute of state.selectedNodeTraceroutes">
<template v-if="props.data.length > 0">
<li @click="$emit('showTraceRoute', traceroute)" v-for="traceroute of props.data">
<div class="relative flex items-center">
<div class="block flex-1 px-4 py-2">
<div class="relative flex min-w-0 flex-1 items-center">

View File

@ -1,18 +1,18 @@
<script setup>
const emit = defineEmits(['goTo']);
const emit = defineEmits(['goTo', 'dismiss']);
const props = defineProps(['data']);
import moment from 'moment';
import { computed } from 'vue';
import { state } from '@/store';
import { useMapStore } from '@/stores/mapStore';
import NodeEntry from '@/components/Traceroute/NodeEntry.vue';
const mapData = useMapStore();
// selected node IDs
const toNode = computed(() => mapData.findNodeById(state.selectedTraceRoute.to));
const fromNode = computed(() => mapData.findNodeById(state.selectedTraceRoute.from));
const gatewayNode = computed(() => mapData.findNodeById(state.selectedTraceRoute.gateway_id));
const toNode = computed(() => mapData.findNodeById(props.data.to));
const fromNode = computed(() => mapData.findNodeById(props.data.from));
const gatewayNode = computed(() => mapData.findNodeById(props.data.gateway_id));
// pre-resolve the route nodes into an array
const routeNodes = computed(() =>
state.selectedTraceRoute.route?.map(id => ({
props.data.route?.map(id => ({
id,
node: mapData.findNodeById(id),
})) ?? []
@ -29,7 +29,7 @@ const routeNodes = computed(() =>
leave-active-class="transition-opacity duration-300 ease-linear"
leave-from-class="opacity-100"
leave-to-class="opacity-0">
<div v-show="state.selectedTraceRoute != null" @click="state.selectedTraceRoute = null" class="fixed inset-0 bg-gray-900/75"></div>
<div v-show="props.data != null" @click="$emit('dismiss')" class="fixed inset-0 bg-gray-900/75"></div>
</transition>
<!-- sidebar -->
@ -40,19 +40,19 @@ const routeNodes = computed(() =>
leave-active-class="transition duration-300 ease-in-out transform"
leave-from-class="translate-x-0"
leave-to-class="-translate-x-full">
<div v-show="state.selectedTraceRoute != null" class="fixed top-0 left-0 bottom-0">
<div v-if="state.selectedTraceRoute != null" class="w-screen h-full max-w-md overflow-hidden">
<div v-show="props.data != null" class="fixed top-0 left-0 bottom-0">
<div v-if="props.data != null" class="w-screen h-full max-w-md overflow-hidden">
<div class="flex h-full flex-col bg-white shadow-xl">
<!-- slideover header -->
<div class="p-2 border-b border-gray-200 shadow-sm">
<div class="flex items-start justify-between">
<div>
<h2 class="font-bold">Traceroute #{{ state.selectedTraceRoute.id }}</h2>
<h3 class="text-sm">{{ moment(new Date(state.selectedTraceRoute.updated_at)).fromNow() }} - {{ state.selectedTraceRoute.route.length }} hops {{ state.selectedTraceRoute.channel_id ? `on ${state.selectedTraceRoute.channel_id}` : '' }}</h3>
<h2 class="font-bold">Traceroute #{{ props.data.id }}</h2>
<h3 class="text-sm">{{ moment(new Date(props.data.updated_at)).fromNow() }} - {{ props.data.route.length }} hops {{ props.data.channel_id ? `on ${props.data.channel_id}` : '' }}</h3>
</div>
<div class="my-auto ml-3 flex h-7 items-center">
<a href="javascript:void(0)" class="rounded-full" @click="state.selectedTraceRoute = null">
<a href="javascript:void(0)" class="rounded-full" @click="$emit('dismiss')">
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
@ -83,7 +83,7 @@ const routeNodes = computed(() =>
<div>
<div class="bg-gray-200 p-2 font-semibold">Raw Data</div>
<div class="text-sm text-gray-700">
<pre class="bg-gray-100 rounded-sm p-2 overflow-x-auto">{{ JSON.stringify(state.selectedTraceRoute, null, 4) }}</pre>
<pre class="bg-gray-100 rounded-sm p-2 overflow-x-auto">{{ JSON.stringify(props.data, null, 4) }}</pre>
</div>
</div>

View File

@ -1,18 +1,14 @@
import { reactive, computed } from 'vue';
import { reactive } from 'vue';
export const state = reactive({
// state
searchText: '', // moved to ui store, maybe should not be there though?
selectedNodeOutlineCircle: null,
selectedNodeMqttMetrics: [],
selectedNodeTraceroutes: [],
selectedNodeDeviceMetrics: [],
selectedNodePowerMetrics: [],
selectedNodeEnvironmentMetrics: [],
// new selected node stuff
// new selected node/data stuff
positionHistoryNode: null,
neighborsNode: null,
traceRouteData: null,
// new modal specific stuff
neighborsModalType: null,
@ -20,9 +16,4 @@ export const state = reactive({
// position history
positionHistoryDateTimeTo: null,
positionHistoryDateTimeFrom: null,
});
export const selectedNodeLatestPowerMetric = computed(() => {
const [ latestPowerMetric ] = state.selectedNodePowerMetrics.slice(-1);
return latestPowerMetric;
});

View File

@ -469,6 +469,14 @@ function goToNode(id, animate, zoom){
}
function showTraceRoute(traceroute) {
state.traceRouteData = traceroute;
}
function resetTraceRoute() {
state.traceRouteData = null;
}
function onSearchResultNodeClick(node) {
// clear search
ui.search('');
@ -967,10 +975,10 @@ onMounted(() => {
<InfoModal />
<HardwareModelList />
<Settings />
<NodeInfo @show-position-history="showNodePositionHistory"/>
<NodeInfo @show-position-history="showNodePositionHistory" @show-trace-route="showTraceRoute"/>
<NodeNeighborsModal @dismiss="resetNodeNeighbors" :node="state.neighborsNode"/>
<NodePositionHistoryModal @dismiss="resetPositionHistory" :node="state.positionHistoryNode"/>
<TracerouteInfo @go-to="goToNode" />
<TracerouteInfo @go-to="goToNode" @dismiss="resetTraceRoute" :data="state.traceRouteData"/>
<Teleport v-if="popupTarget && selectedNode" :to="popupTarget">
<NodeTooltip :node="selectedNode" @show-neighbors="showNodeNeighbors"/>
</Teleport>