Basic stats working

This commit is contained in:
Douglas Barone 2022-06-15 11:35:20 +00:00
parent 2f6ee379c5
commit b45b05f033
11 changed files with 317 additions and 9 deletions

View File

@ -46,7 +46,7 @@ cron.schedule('0 */2 * * * *', () => {
updateAccessPoints().catch(console.log)
})
cron.schedule('0 */5 * * * *', () => {
cron.schedule('0 */1 * * * *', () => {
generateStatsForAllAccessPoints()
})

View File

@ -107,7 +107,7 @@ export async function getOnlineWifiDevices() {
apName: client.AP,
status: client.ST == 'Online' ? 'ONLINE' : 'OFFLINE',
controller: 'Cisco',
signalStrength: client.SS,
signalStrength: client.SS || null,
frequency: client.FB,
protocol: client.PT,
speed: client.SD,

View File

@ -191,7 +191,7 @@ export async function getOnlineWifiDevices() {
apName: accessPoints[0].find(ap => ap.mac === client.ap_mac).name,
status: 'ONLINE',
controller: 'UniFi',
signalStrength: client.signal,
signalStrength: client.signal || null,
frequency: null,
protocol: null,
speed: Math.floor(client.tx_rate / 1000),

View File

@ -42,7 +42,9 @@ async function generateStatsForAccessPoint(mac) {
timestamp: timestamp.toISOString(),
clients: dbStats._count._all,
avgSignalStrength: Math.floor(dbStats._avg.signalStrength, 0),
avgSignalStrength: dbStats.clients
? Math.floor(dbStats._avg.signalStrength, 0)
: null,
minSignalStrength: dbStats._min.signalStrength,
maxSignalStrength: dbStats._max.signalStrength,

View File

@ -20,15 +20,21 @@ export const AccessPoint = {
usage: (parent, data, context, info) => parent.usage.toString(),
stats: async (parent, { take, dateIn, dateOut }, context, info) =>
prisma.wifiStats.findMany({
stats: async (parent, { take, dateIn, dateOut }, context, info) => {
const stats = await prisma.wifiStats.findMany({
where: {
accessPoint: {
id: parent.id
id: parent.id,
timestamp_gte: dateIn,
timestamp_lte: dateOut
}
},
orderBy: { timestamp: 'desc' },
take
}),
})
return stats.reverse()
},
latestStats: async (parent, data, context, info) =>
prisma.wifiStats.findFirst({

View File

@ -1,4 +1,5 @@
const WifiStats = {
timestamp: parent => parent.timestamp.toString(),
avgUsage: parent => parent.avgUsage.toString(),
sumUsage: parent => parent.sumUsage.toString()
}

27
web/package-lock.json generated
View File

@ -11,6 +11,7 @@
"@mdi/font": "^6.6.96",
"apollo-link-ws": "^1.0.20",
"apollo-utilities": "^1.3.4",
"chart.js": "^3.8.0",
"date-fns": "^2.28.0",
"eslint": "^6.8.0",
"qrcode.vue": "^1.7.0",
@ -18,6 +19,7 @@
"validator": "^13.7.0",
"vue": "^2.6.14",
"vue-apollo": "^3.1.0",
"vue-chartjs": "^4.1.1",
"vue-json-pretty": "^1.8.2",
"vue-router": "^3.5.3",
"vue-the-mask": "^0.11.1",
@ -8789,6 +8791,11 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"node_modules/chart.js": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz",
"integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg=="
},
"node_modules/check-types": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
@ -22410,6 +22417,15 @@
"graphql-tag": "^2"
}
},
"node_modules/vue-chartjs": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-4.1.1.tgz",
"integrity": "sha512-rKIQ3jPrjhwxjKdNJppnYxRuBSrx4QeM3nNHsfIxEqjX6QS4Jq6e6vnZBxh2MDpURDC2uvuI2N0eIt1cWXbBVA==",
"peerDependencies": {
"chart.js": "^3.7.0",
"vue": "^3.0.0-0 || ^2.6.0"
}
},
"node_modules/vue-cli-plugin-apollo": {
"version": "0.22.2",
"resolved": "https://registry.npmjs.org/vue-cli-plugin-apollo/-/vue-cli-plugin-apollo-0.22.2.tgz",
@ -31273,6 +31289,11 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"chart.js": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz",
"integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg=="
},
"check-types": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
@ -42126,6 +42147,12 @@
"throttle-debounce": "^2.1.0"
}
},
"vue-chartjs": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-4.1.1.tgz",
"integrity": "sha512-rKIQ3jPrjhwxjKdNJppnYxRuBSrx4QeM3nNHsfIxEqjX6QS4Jq6e6vnZBxh2MDpURDC2uvuI2N0eIt1cWXbBVA==",
"requires": {}
},
"vue-cli-plugin-apollo": {
"version": "0.22.2",
"resolved": "https://registry.npmjs.org/vue-cli-plugin-apollo/-/vue-cli-plugin-apollo-0.22.2.tgz",

View File

@ -12,6 +12,7 @@
"@mdi/font": "^6.6.96",
"apollo-link-ws": "^1.0.20",
"apollo-utilities": "^1.3.4",
"chart.js": "^3.8.0",
"date-fns": "^2.28.0",
"eslint": "^6.8.0",
"qrcode.vue": "^1.7.0",
@ -19,6 +20,7 @@
"validator": "^13.7.0",
"vue": "^2.6.14",
"vue-apollo": "^3.1.0",
"vue-chartjs": "^4.1.1",
"vue-json-pretty": "^1.8.2",
"vue-router": "^3.5.3",
"vue-the-mask": "^0.11.1",

View File

@ -0,0 +1,108 @@
<template>
<div>
<LineChart
:chart-options="chartOptions"
:chart-data="chartData"
:chart-id="chartId"
:dataset-id-key="datasetIdKey"
:plugins="plugins"
:css-classes="cssClasses"
:styles="styles"
:width="width"
:height="height"
/>
</div>
</template>
<script>
import { Line as LineChart } from 'vue-chartjs/legacy'
import { time } from '../../plugins/date'
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
CategoryScale,
PointElement
} from 'chart.js'
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
CategoryScale,
PointElement
)
export default {
components: { LineChart },
props: {
stats: {
type: Array,
required: true
},
chartId: {
type: String,
default: 'line-chart'
},
datasetIdKey: {
type: String,
default: 'label'
},
width: {
type: Number,
default: 400
},
height: {
type: Number,
default: 250
},
cssClasses: {
default: '',
type: String
},
styles: {
type: Object,
default: () => {}
},
plugins: {
type: Array,
default: () => []
}
},
data: () => ({
chartOptions: {
responsive: true,
maintainAspectRatio: false
}
}),
computed: {
chartData() {
return {
labels: this.stats.map(stat => time(stat.timestamp)),
datasets: [
{
label: 'Clientes conectados',
backgroundColor: this.$vuetify.theme.isDark
? this.$vuetify.theme.themes.light.primary
: this.$vuetify.theme.themes.dark.primary,
borderColor: this.$vuetify.theme.isDark
? this.$vuetify.theme.themes.light.primary
: this.$vuetify.theme.themes.dark.primary,
borderWidth: 1,
data: this.stats.map(stat => stat.clients)
}
]
}
}
}
}
</script>
<style></style>

View File

@ -0,0 +1,130 @@
<template>
<div>
<LineChart
:chart-options="chartOptions"
:chart-data="chartData"
:chart-id="chartId"
:dataset-id-key="datasetIdKey"
:plugins="plugins"
:css-classes="cssClasses"
:styles="styles"
:width="width"
:height="height"
/>
</div>
</template>
<script>
import { Line as LineChart } from 'vue-chartjs/legacy'
import { time } from '../../plugins/date'
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
CategoryScale,
PointElement
} from 'chart.js'
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
CategoryScale,
PointElement
)
export default {
components: { LineChart },
props: {
stats: {
type: Array,
required: true
},
chartId: {
type: String,
default: 'line-chart'
},
datasetIdKey: {
type: String,
default: 'label'
},
width: {
type: Number,
default: 400
},
height: {
type: Number,
default: 250
},
cssClasses: {
default: '',
type: String
},
styles: {
type: Object,
default: () => {}
},
plugins: {
type: Array,
default: () => []
}
},
data: () => ({
chartOptions: {
responsive: true,
maintainAspectRatio: false
}
}),
computed: {
chartData() {
return {
labels: this.stats.map(stat => time(stat.timestamp)),
datasets: [
{
label: 'Média do sinal',
backgroundColor: this.$vuetify.theme.isDark
? this.$vuetify.theme.themes.light.secondary
: this.$vuetify.theme.themes.dark.secondary,
borderColor: this.$vuetify.theme.isDark
? this.$vuetify.theme.themes.light.secondary
: this.$vuetify.theme.themes.dark.secondary,
borderWidth: 1,
data: this.stats.map(stat => stat.avgSignalStrength)
},
{
label: 'Pior sinal',
backgroundColor: this.$vuetify.theme.isDark
? this.$vuetify.theme.themes.light.error
: this.$vuetify.theme.themes.dark.error,
borderColor: this.$vuetify.theme.isDark
? this.$vuetify.theme.themes.light.error
: this.$vuetify.theme.themes.dark.error,
borderWidth: 1,
data: this.stats.map(stat => stat.minSignalStrength)
},
{
label: 'Melhor sinal',
backgroundColor: this.$vuetify.theme.isDark
? this.$vuetify.theme.themes.light.success
: this.$vuetify.theme.themes.dark.success,
borderColor: this.$vuetify.theme.isDark
? this.$vuetify.theme.themes.light.success
: this.$vuetify.theme.themes.dark.success,
borderWidth: 1,
data: this.stats.map(stat => stat.maxSignalStrength)
}
]
}
}
}
}
</script>
<style></style>

View File

@ -41,6 +41,17 @@
</v-card-title>
<v-divider />
<v-card-text>
<v-container fluid>
<v-row>
<v-col>
<AccessPointClientsChart :stats="accessPoint.stats" />
</v-col>
<v-col>
<AccessPointSignalStrengthChart :stats="accessPoint.stats" />
</v-col>
</v-row>
</v-container>
<v-divider />
<AccessPointClientsDataTable
:wifi-devices="accessPoint.wifiDevices"
:filter="filter"
@ -65,10 +76,17 @@ import gql from 'graphql-tag'
import ApIcon from '../../components/ApIcon.vue'
import AccessPointClientsDataTable from '../../components/DataTables/AccessPointClientsDataTable.vue'
import AccessPointClientsChart from '../../components/Charts/AccessPointClientsChart.vue'
import AccessPointSignalStrengthChart from '../../components/Charts/AccessPointSignalStrengthChart.vue'
export default {
name: 'SingleAccessPoint',
components: { ApIcon, AccessPointClientsDataTable },
components: {
ApIcon,
AccessPointClientsDataTable,
AccessPointClientsChart,
AccessPointSignalStrengthChart
},
data: () => ({
showDialog: true,
filter: ''
@ -99,6 +117,19 @@ export default {
controller
notes
updatedAt
stats(take: 100) {
id
timestamp
clients
avgSignalStrength
minSignalStrength
maxSignalStrength
avgClientUptime
maxClientUptime
avgUsage
}
wifiDevices {
id
hostname
@ -112,6 +143,7 @@ export default {
protocol
speed
usage
user {
displayName
sAMAccountName