Skip to content

Commit

Permalink
feat(astrapia): add netflow info onto home page
Browse files Browse the repository at this point in the history
- Application Status Cards
- Flow Import Metric Cards
- move Flow Import Graph to Home Page
- add axis labels to Flow Import Graph
- use window.innerWidth/Height for Graph Dimensions
- remove Network Analysis Page

- extend Metric Services

---------

Co-authored-by: Denes TAKACS <denes.takacs@protonmail.com>
  • Loading branch information
necoder27 and CodeCrimeInvestigator authored Mar 9, 2024
1 parent 383cb1e commit 8abf83d
Show file tree
Hide file tree
Showing 19 changed files with 707 additions and 109 deletions.
58 changes: 47 additions & 11 deletions Astrapia/components/FlowImportGraph.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,33 @@
</template>

<script setup lang="ts">
import {onMounted, ref} from 'vue';
import {onMounted, ref, watch} from 'vue';
import {axisBottom, axisLeft, extent, isoParse, line, pointer, scaleLinear, scaleTime, select,} from 'd3';
import networkAnalysisService from '~/services/networkAnalysisService';
import networkAnalysisService from '~/services/metricService';
const props = defineProps({
from: {
type: String
},
to: {
type: String
}
});
let svgRef = ref(null);
const from = ref(props.from);
const to = ref(props.to);
onMounted(async() => {
const flowImportGraphData = await networkAnalysisService.getFlowImport() as FlowImport[];
async function renderGraph() {
select(svgRef.value).selectAll("*").remove();
const flowImportGraphData = await networkAnalysisService.getFlowImport(props.from!, props.to!) as FlowImport[];
const width = 1600;
const height = 600;
const marginTop = 20;
const marginRight = 170;
const marginBottom = 20;
const marginLeft = 20;
const width = window.innerWidth * 0.9;
const height = window.innerHeight * 0.6;
const marginTop = window.innerHeight * 0.02;
const marginRight = window.innerWidth * 0.2;
const marginBottom = window.innerHeight * 0.14;
const marginLeft = window.innerWidth * 0.03;
let tooltip = select('#flowImportContainer')
.append('div')
Expand Down Expand Up @@ -61,6 +72,22 @@ onMounted(async() => {
.attr('transform', `translate(${marginLeft},0)`)
.call(axisLeft(y));
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width - marginRight)
.attr("y", height - marginBottom * 1.1)
.text("Time");
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Number of Packets");
svg.selectAll('xGrid')
.data(x.ticks())
.join('line')
Expand Down Expand Up @@ -134,7 +161,16 @@ onMounted(async() => {
.attr('y', marginTop)
.classed('chart-title', true)
.text('Flow Import');
}
watch (() => [props.from, props.to], async ([newFrom, newTo]) => {
await renderGraph();
});
onMounted(async() => {
await renderGraph();
});
export interface FlowImport {
dateTime: string;
endpoints: { [key: string]: number };
Expand Down
51 changes: 51 additions & 0 deletions Astrapia/components/FlowImportGraphTimeframeSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<div class="flow-import-graph-timeframe-selector">
<p class="flow-import-graph-labels">from:</p>
<input class="flow-import-graph-selectors" type="datetime-local" v-model="from">
<p class="flow-import-graph-labels">to:</p>
<input class="flow-import-graph-selectors" type="datetime-local" v-model="to">
</div>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';
const props = defineProps({
fromValue: {
type: String
},
toValue: {
type: String
},
});
const from = ref(props.fromValue);
const to = ref(props.toValue);
const emit = defineEmits<{
change: [from: string, to: string];
}>();
watch([from, to], ([newFrom, newTo]) => {
emit('change', newFrom!, newTo!)
})
</script>

<style scoped>
.flow-import-graph-timeframe-selector {
display: flex;
align-items: center;
margin: 4vh 0 4vh 2.5vw;
width: 92.5vw;
height: 2vh;
}
.flow-import-graph-labels {
font-family: 'Open Sans', sans-serif;
}
.flow-import-graph-selectors {
margin: 0 2vw 0 1vw;
font-family: 'Open Sans', sans-serif;
}
</style>
22 changes: 22 additions & 0 deletions Astrapia/components/FlowImporterGraphContainer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<FlowImportGraphTimeframeSelector :from-value="from" :to-value="to" @change="updateTimeframe" />
<FlowImportGraph :from="from" :to="to" />
</template>

<script setup lang="ts">
import {ref} from 'vue';
import FlowImportGraph from "~/components/FlowImportGraph.vue";
import FlowImportGraphTimeframeSelector from "~/components/FlowImportGraphTimeframeSelector.vue";
const from = ref(new Date(new Date().getTime() - 5 * 60 * 1000).toISOString().slice(0,16));
const to = ref(new Date().toISOString().slice(0,16));
const updateTimeframe = (newFrom: string, newTo: string) => {
from.value = newFrom;
to.value = newTo;
};
</script>

<style scoped>
</style>
69 changes: 69 additions & 0 deletions Astrapia/components/FlowMetricCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<div class="flow-metric-card">
<p class="flow-metric-card-key">{{ props.keyProp }}:</p>
<div v-for="(value, key) in props.data" :key="key" class="data-line">
<span class="data-key">{{ key }}: </span>
<span class="data-value">{{ formatNumber(value) }}</span>
</div>
</div>
</template>

<script setup lang="ts">
interface GeneralFlowImporterData {
receivedPacketCount: number,
receivedByteCount: number,
transmittedPacketCount: number,
transmittedByteCount: number,
successfullyParsedPacket: number,
failedParsedPacket: number
}
const props = defineProps<{
data: GeneralFlowImporterData;
keyProp: string;
}>();
const formatNumber = (value: any) => {
let numValue = Number(value);
if (!isNaN(numValue)) {
return numValue.toLocaleString();
}
return value;
}
</script>

<style scoped>
.flow-metric-card {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
width: auto;
height: auto;
border: 1px solid #424242;
border-radius: 4px;
padding: 1.5vh 1vw;
box-shadow: 4px 4px 8px 0 #e0e0e0;
user-select: none;
margin-right: 1.5807942vw;
margin-bottom: 4vh;
}
.flow-metric-card-key {
font-weight: bold;
margin: 0;
}
.data-line {
text-indent: 1em;
}
.data-key {
font-weight: bold;
color: #4D4D4D;
}
.data-value {
font-weight: bold;
}
</style>
32 changes: 32 additions & 0 deletions Astrapia/components/FlowMetricCardsContainer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<div class="flow-metric-cards-container">
<FlowMetricCard v-for="(data, key) in metrics" :key="key" :data="data" :keyProp="key" />
</div>
</template>

<script setup lang="ts">
import {onMounted, ref} from 'vue'
import metricService from "~/services/metricService";
import FlowMetricCard from "~/components/FlowMetricCard.vue";
const metrics = ref({});
async function getGeneralFlowImporterMetrics() {
const metricData = await metricService.getGeneralFlowImporterData();
metrics.value = metricData;
}
onMounted(async () => {
await getGeneralFlowImporterMetrics();
});
</script>

<style scoped>
.flow-metric-cards-container {
display: flex;
flex-wrap: wrap;
margin: 4vh 0 4vh 2.5vw;
width: 92.5vw;
height: auto;
}
</style>
115 changes: 115 additions & 0 deletions Astrapia/components/SelfTestCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<template>
<div class="self-test-card">
<p class="self-test-card-key">{{ props.keyProp.toUpperCase() }}</p>
<div v-for="(value, key) in props.data" :key="key" class="data-line">
<span class="data-key">{{ key }}: </span>
<span v-if="isObject(value)" class="nested-key-value" v-html="objectToString(value)"></span>
<span v-else class="data-value">{{ formatNumber(value) }}&nbsp;</span>
<span v-if="typeof value === 'boolean'" :class="value ? 'true-value' : 'false-value'"></span>
</div>
</div>
</template>

<script setup lang="ts">
const props = defineProps< {
data: any;
keyProp: string;
}>();
const isObject = (item: any) => {
return (typeof item === "object" && !Array.isArray(item) && item !== null);
}
const objectToString = (obj: any) => {
let str = '';
for (const [key, value] of Object.entries(obj)) {
let formattedValue = value;
if (key !== 'Port' && !isNaN(Number(value))) {
formattedValue = Number(value).toLocaleString();
}
if (typeof value === 'boolean') {
str += `<div><span class="keys">${key}</span>: <span class="values">${formattedValue}</span> <span class="${value ? 'true-value' : 'false-value'}"></span></div>`;
} else {
str += `<div><span class="keys">${key}</span>: <span class="values">${formattedValue}</span></div>`;
}
}
return str;
}
const formatNumber = (value: any, key: string) => {
if (key !== 'Port' && !isNaN(Number(value))) {
return Number(value).toLocaleString();
}
return value;
}
</script>

<style scoped>
.self-test-card {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
width: auto;
height: auto;
border: 1px solid #424242;
border-radius: 4px;
padding: 1.5vh 1vw;
box-shadow: 4px 4px 8px 0 #e0e0e0;
user-select: none;
margin-right: 0.55vw;
margin-bottom: 4vh;
}
.self-test-card-key {
font-weight: bold;
margin: 0 0 1vh 0;
font-size: 2vh;
color: #294D61;
}
.data-line {
text-indent: 1em;
}
.data-key {
font-weight: bold;
}
.data-value {
font-weight: bold;
color: #005a8a;
}
.nested-key-value {
text-indent: 2em;
}
.self-test-card:deep(.keys) {
color: #4D4D4D;
font-weight: bold;
}
.self-test-card:deep(.values) {
color: #005a8a;
font-weight: bold;
}
.self-test-card:deep(.true-value) {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #33cc33;
margin-right: 5px;
}
.self-test-card:deep(.false-value) {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: red;
margin-right: 5px;
}
</style>
Loading

0 comments on commit 8abf83d

Please sign in to comment.