Basic stats working
This commit is contained in:
parent
2f6ee379c5
commit
b45b05f033
|
@ -46,7 +46,7 @@ cron.schedule('0 */2 * * * *', () => {
|
|||
updateAccessPoints().catch(console.log)
|
||||
})
|
||||
|
||||
cron.schedule('0 */5 * * * *', () => {
|
||||
cron.schedule('0 */1 * * * *', () => {
|
||||
generateStatsForAllAccessPoints()
|
||||
})
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
27
web/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
108
web/src/components/Charts/AccessPointClientsChart.vue
Normal file
108
web/src/components/Charts/AccessPointClientsChart.vue
Normal 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>
|
130
web/src/components/Charts/AccessPointSignalStrengthChart.vue
Normal file
130
web/src/components/Charts/AccessPointSignalStrengthChart.vue
Normal 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>
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user