Refatored UserPrecense functionality

This commit is contained in:
Douglas Barone 2020-12-17 17:45:12 -04:00
parent 9945b58fb1
commit 4928dd3a73
12 changed files with 192 additions and 205 deletions

View File

@ -7,7 +7,7 @@ import prisma from '../prisma'
import { pubsub, USER_PRESENCE_UPDATED } from '../pubsub' import { pubsub, USER_PRESENCE_UPDATED } from '../pubsub'
const DEBOUNCE_TIME_MS = 10000 const DEBOUNCE_TIME_MS = 10000
const RECENT_THRESHOLD_IN_MINUTES = 5 const RECENT_THRESHOLD_IN_MINUTES = 3
let working = false let working = false

View File

@ -116,31 +116,33 @@ const Query = {
search = search.toLowerCase().trim() search = search.toLowerCase().trim()
const userPresences = usersWithWifiDevices const filteredUsers = search
.filter( ? usersWithWifiDevices.filter(
user => user =>
user.displayName.toLowerCase().includes(search) || user.displayName.toLowerCase().includes(search) ||
user.sAMAccountName.toLowerCase().includes(search) || user.sAMAccountName.toLowerCase().includes(search) ||
user.wifiDevices.some( user.wifiDevices.some(
device => device =>
device.apName.toLowerCase().includes(search) || device.apName.toLowerCase().includes(search) ||
device.ip.startsWith(search) device.ip.startsWith(search)
) )
) )
.map(user => ({ : usersWithWifiDevices
user: {
id: user.id,
displayName: user.displayName,
thumbnailPhoto: user.thumbnailPhoto
},
wifiDevices: user.wifiDevices
}))
const sortedUserPresences = userPresences.sort((a, b) => const sortedUsers = filteredUsers.sort((a, b) =>
a.wifiDevices[0].lastSeen > b.wifiDevices[0].lastSeen ? -1 : 1 a.wifiDevices[0].lastSeen > b.wifiDevices[0].lastSeen ? -1 : 1
) )
return sortedUserPresences.slice(0, 200) return sortedUsers
.map(userPresence => ({
id: userPresence.id,
displayName: userPresence.displayName,
thumbnailPhoto: userPresence.thumbnailPhoto,
lastSeen: userPresence.wifiDevices[0].lastSeen,
status: userPresence.wifiDevices[0].status,
apName: userPresence.wifiDevices[0].apName
}))
.slice(0, 200)
} }
} }

View File

@ -0,0 +1,6 @@
const UserPresence = {
displayName: _ => (_.displayName ? _.displayName.capitalize() : ''),
lastSeen: _ => _.lastSeen?.toISOString()
}
export { UserPresence }

View File

@ -1,9 +1,6 @@
const WifiDevice = { const WifiDevice = {
lastSeen: _ => _.lastSeen?.toISOString(), lastSeen: _ => _.lastSeen?.toISOString(),
firstSeen: _ => _.firstSeen?.toISOString(), firstSeen: _ => _.firstSeen?.toISOString()
isOnline: _ => _.status == 'ONLINE',
isRecent: _ => _.status == 'RECENT',
isOffline: _ => _.status == 'OFFLINE'
} }
export { WifiDevice } export { WifiDevice }

View File

@ -3,6 +3,7 @@ import { Mutation } from './Mutation'
import { Subscription } from './Subscriptions' import { Subscription } from './Subscriptions'
import { User } from './User' import { User } from './User'
import { UserPresence } from './UserPresence'
import { Group } from './Group' import { Group } from './Group'
import { ResetToken } from './ResetToken' import { ResetToken } from './ResetToken'
import { WifiDevice } from './WifiDevice' import { WifiDevice } from './WifiDevice'
@ -13,6 +14,7 @@ const resolvers = {
Mutation, Mutation,
Subscription, Subscription,
User, User,
UserPresence,
Group, Group,
ResetToken, ResetToken,
WifiDevice, WifiDevice,

View File

@ -167,14 +167,15 @@ const typeDefs = gql`
uptime: String uptime: String
apName: String apName: String
status: Status status: Status
isOnline: Boolean
isRecent: Boolean
isOffline: Boolean
} }
type UserPresence { type UserPresence {
user: User! id: ID!
wifiDevices: [WifiDevice!]! displayName: String!
thumbnailPhoto: String
lastSeen: String!
status: Status!
apName: String!
} }
enum Status { enum Status {

View File

@ -1,139 +0,0 @@
<template>
<div v-if="!loading">
<transition-group
name="scale-transition"
tag="div"
class="layout row row--dense wrap"
mode="out-in"
>
<v-col
v-for="userPresence in userPresences"
:key="userPresence.user.id"
cols="12"
sm="6"
md="4"
lg="3"
>
<v-card
outlined
class="border-highlight light-shadow"
:class="{
online: userPresence.wifiDevices[0].isOnline,
recent: userPresence.wifiDevices[0].isRecent
}"
>
<v-list three-line>
<v-list-item>
<v-list-item-avatar size="52px" style="overflow: visible">
<v-badge
:color="
userPresence.wifiDevices.some(
wifiDevice => wifiDevice.isOnline
)
? 'green darken-1'
: 'grey darken-1'
"
bottom
offset-x="18px"
offset-y="18px"
>
<Avatar :src="userPresence.user.thumbnailPhoto" size="52px" />
</v-badge>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>
{{ userPresence.user.displayName }}
</v-list-item-title>
<template v-if="userPresence.wifiDevices[0].isOnline">
<v-list-item-subtitle>
<span class="font-weight-medium">On-line</span>
</v-list-item-subtitle>
<v-list-item-subtitle>
Próximo ao AP
<span class="font-weight-medium">
{{ userPresence.wifiDevices[0].apName }}
</span>
</v-list-item-subtitle>
</template>
<template v-if="userPresence.wifiDevices[0].isRecent">
<v-list-item-subtitle>
<span class="font-weight-medium">Visto recentemente</span>
</v-list-item-subtitle>
<v-list-item-subtitle>
Próximo ao AP
<span class="font-weight-medium">
{{ userPresence.wifiDevices[0].apName }}
</span>
</v-list-item-subtitle>
</template>
<template v-if="userPresence.wifiDevices[0].isOffline">
<v-list-item-subtitle>
<span class="font-weight-medium">Off-line</span>
</v-list-item-subtitle>
<v-list-item-subtitle>
<span class="font-weight-medium">
Visto {{ userPresence.wifiDevices[0].lastSeen | from }}
</span>
</v-list-item-subtitle>
</template>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card>
</v-col>
</transition-group>
</div>
<div v-else>
<div class="layout row row--dense wrap">
<v-col v-for="i in 12" :key="i" cols="12" sm="6" md="4" lg="3">
<v-card outlined class="border-highlight light-shadow">
<v-list three-line>
<v-list-item>
<v-list-item-avatar>
<v-skeleton-loader type="avatar" />
</v-list-item-avatar>
<v-list-item-content>
<v-skeleton-loader type="paragraph" style="max-width: 300px" />
</v-list-item-content>
</v-list-item>
</v-list>
</v-card>
</v-col>
</div>
</div>
</template>
<script>
import Avatar from './Avatar'
export default {
name: 'UserPresenceStatusList',
components: { Avatar },
props: {
userPresences: {
type: Array,
default: () => []
},
loading: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped lang="scss">
.border-highlight {
border-left: 4px solid grey;
}
.online {
border-left-color: #117d4c;
}
.recent {
border-left-color: #ffc107;
}
</style>

View File

@ -3,7 +3,9 @@
<v-expansion-panel-header> <v-expansion-panel-header>
<v-icon <v-icon
class="shrink mr-2" class="shrink mr-2"
:color="wifiDevice.isOnline ? 'green darken-1' : 'grey lighten-1'" :color="
wifiDevice.status == 'ONLINE' ? 'green darken-1' : 'grey lighten-1'
"
> >
mdi-cellphone-link mdi-cellphone-link
</v-icon> </v-icon>
@ -13,7 +15,7 @@
</v-expansion-panel-header> </v-expansion-panel-header>
<v-expansion-panel-content> <v-expansion-panel-content>
<v-list dense> <v-list dense>
<v-list-item v-if="wifiDevice.isOnline"> <v-list-item v-if="wifiDevice.status == 'ONLINE'">
<v-list-item-action> <v-list-item-action>
<v-icon color="green darken-1">mdi-wifi</v-icon> <v-icon color="green darken-1">mdi-wifi</v-icon>
</v-list-item-action> </v-list-item-action>

View File

@ -42,8 +42,8 @@ export default {
lastSeen lastSeen
mac mac
oui oui
isOnline
uptime uptime
status
} }
} }
} }

View File

@ -51,12 +51,113 @@
</v-scale-transition> </v-scale-transition>
</div> </div>
<UserPresenceStatusList <transition-group
:user-presences="pagedUserPresences" v-if="
:loading=" !$apollo.queries.userPresence.loading && pagedUserPresences.length
$apollo.queries.userPresence.loading && !pagedUserPresences.length
" "
/> name="scale-transition"
tag="div"
class="layout row row--dense wrap"
mode="out-in"
>
<v-col
v-for="userPresence in pagedUserPresences"
:key="userPresence.id"
cols="12"
sm="6"
md="4"
lg="3"
>
<v-card
outlined
class="border-highlight light-shadow"
:class="{
online: userPresence.status == 'ONLINE',
recent: userPresence.status == 'RECENT'
}"
>
<v-list three-line>
<v-list-item>
<v-list-item-avatar size="52px" style="overflow: visible">
<v-badge
:color="color(userPresence.status)"
bottom
offset-x="18px"
offset-y="18px"
>
<Avatar :src="userPresence.thumbnailPhoto" size="52px" />
</v-badge>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>
{{ userPresence.displayName }}
</v-list-item-title>
<template v-if="userPresence.status == 'ONLINE'">
<v-list-item-subtitle>
<span class="font-weight-medium">On-line</span>
</v-list-item-subtitle>
<v-list-item-subtitle>
Próximo ao AP
<span class="font-weight-medium">
{{ userPresence.apName }}
</span>
</v-list-item-subtitle>
</template>
<template v-if="userPresence.status == 'RECENT'">
<v-list-item-subtitle>
<span class="font-weight-medium">Visto recentemente</span>
</v-list-item-subtitle>
<v-list-item-subtitle>
Próximo ao AP
<span class="font-weight-medium">
{{ userPresence.apName }}
</span>
</v-list-item-subtitle>
</template>
<template v-if="userPresence.status == 'OFFLINE'">
<v-list-item-subtitle>
<span class="font-weight-medium">Off-line</span>
</v-list-item-subtitle>
<v-list-item-subtitle>
<span class="font-weight-medium">
Visto
{{ userPresence.lastSeen | from }}
</span>
</v-list-item-subtitle>
</template>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card>
</v-col>
</transition-group>
<div v-else>
<div class="layout row row--dense wrap">
<v-col v-for="i in 12" :key="i" cols="12" sm="6" md="4" lg="3">
<v-card outlined class="border-highlight light-shadow">
<v-list three-line>
<v-list-item>
<v-list-item-avatar>
<v-skeleton-loader type="avatar" />
</v-list-item-avatar>
<v-list-item-content>
<v-skeleton-loader
type="paragraph"
style="max-width: 300px"
/>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card>
</v-col>
</div>
</div>
<v-alert <v-alert
v-if="!pagedUserPresences.length && search" v-if="!pagedUserPresences.length && search"
icon="mdi-account-search" icon="mdi-account-search"
@ -124,11 +225,11 @@
<script> <script>
import gql from 'graphql-tag' import gql from 'graphql-tag'
import UserPresenceStatusList from '../components/UserPresenceStatusList' import Avatar from '../components/Avatar'
export default { export default {
name: 'UserPresence', name: 'UserPresence',
components: { UserPresenceStatusList }, components: { Avatar },
data: () => { data: () => {
const pageSize = 12 const pageSize = 12
return { return {
@ -143,20 +244,12 @@ export default {
query: gql` query: gql`
query($search: String = "") { query($search: String = "") {
userPresence(search: $search) { userPresence(search: $search) {
user { id
id displayName
displayName thumbnailPhoto
thumbnailPhoto lastSeen
} status
wifiDevices { apName
apName
id
lastSeen
isOnline
isRecent
isOffline
controller
}
} }
} }
`, `,
@ -194,8 +287,27 @@ export default {
pagedUserPresences() { pagedUserPresences() {
return this.userPresence?.slice(0, this.resultSize) || [] return this.userPresence?.slice(0, this.resultSize) || []
} }
},
methods: {
color(status) {
return {
ONLINE: 'green darken-2',
RECENT: 'orange darken-1',
OFFLINE: 'grey darken-1'
}[status]
}
} }
} }
</script> </script>
<style scoped></style> <style scoped lang="scss">
.border-highlight {
border-left: 4px solid grey;
}
.online {
border-left-color: green;
}
.recent {
border-left-color: #ffc107;
}
</style>

View File

@ -33,7 +33,8 @@
<div v-if="wifiDevices" class="text-center my-4"> <div v-if="wifiDevices" class="text-center my-4">
<v-chip color="primary" class="mr-1" dark> <v-chip color="primary" class="mr-1" dark>
{{ {{
wifiDevices && wifiDevices.filter(device => device.isOnline).length wifiDevices &&
wifiDevices.filter(device => device.status == 'ONLINE').length
}} }}
</v-chip> </v-chip>
online de online de
@ -56,7 +57,7 @@
<v-row dense align-content="center" no-gutters> <v-row dense align-content="center" no-gutters>
<v-col class="shrink" align-self="center"> <v-col class="shrink" align-self="center">
<v-badge <v-badge
:color="device.isOnline ? 'green' : 'grey'" :color="device.status == 'ONLINE' ? 'green' : 'grey'"
left left
offset-y="16" offset-y="16"
dot dot
@ -84,7 +85,7 @@
</v-expansion-panel-header> </v-expansion-panel-header>
<v-expansion-panel-content> <v-expansion-panel-content>
<v-list dense> <v-list dense>
<v-list-item v-if="device.isOnline"> <v-list-item v-if="device.status == 'ONLINE'">
<v-list-item-action> <v-list-item-action>
<v-icon color="green darken-1">mdi-wifi</v-icon> <v-icon color="green darken-1">mdi-wifi</v-icon>
</v-list-item-action> </v-list-item-action>
@ -234,7 +235,7 @@ export default {
ip ip
uptime uptime
apName apName
isOnline status
} }
} }
` `

View File

@ -60,7 +60,7 @@
offset-y="10" offset-y="10"
:content=" :content="
user.wifiDevices user.wifiDevices
.filter(wifiDevice => !wifiDevice.isOnline) .filter(wifiDevice => !wifiDevice.status == 'ONLINE')
.length.toString() .length.toString()
" "
> >
@ -70,7 +70,7 @@
offset-y="10" offset-y="10"
:content=" :content="
user.wifiDevices user.wifiDevices
.filter(wifiDevice => wifiDevice.isOnline) .filter(wifiDevice => wifiDevice.status == 'ONLINE')
.length.toString() .length.toString()
" "
> >
@ -87,7 +87,10 @@
> >
<v-expansion-panel-header> <v-expansion-panel-header>
<div> <div>
<v-icon left :color="device.isOnline ? 'green' : ''"> <v-icon
left
:color="device.status == 'ONLINE' ? 'green' : ''"
>
mdi-cellphone-wireless mdi-cellphone-wireless
</v-icon> </v-icon>
{{ device.hostname || device.mac }} {{ device.oui }} {{ device.hostname || device.mac }} {{ device.oui }}
@ -95,7 +98,7 @@
</v-expansion-panel-header> </v-expansion-panel-header>
<v-expansion-panel-content> <v-expansion-panel-content>
<v-list dense> <v-list dense>
<v-list-item v-if="device.isOnline"> <v-list-item v-if="device.status == 'ONLINE'">
<v-list-item-action> <v-list-item-action>
<v-icon color="green darken-1">mdi-wifi</v-icon> <v-icon color="green darken-1">mdi-wifi</v-icon>
</v-list-item-action> </v-list-item-action>
@ -214,7 +217,7 @@ export default {
return this.wifiUsers?.map(user => ({ return this.wifiUsers?.map(user => ({
...user, ...user,
ips: user.wifiDevices.reduce((ips, device) => ` ${device.ip}`, ''), ips: user.wifiDevices.reduce((ips, device) => ` ${device.ip}`, ''),
wifiDevices: user.wifiDevices.sort(a => (a.isOnline ? -1 : 1)) wifiDevices: user.wifiDevices.sort(a => (a.status == 'ONLINE' ? -1 : 1))
})) }))
} }
}, },
@ -234,7 +237,7 @@ export default {
hostname hostname
firstSeen firstSeen
lastSeen lastSeen
isOnline status
apName apName
essid essid
ip ip