Refatored UserPrecense functionality
This commit is contained in:
parent
9945b58fb1
commit
4928dd3a73
|
@ -7,7 +7,7 @@ import prisma from '../prisma'
|
|||
import { pubsub, USER_PRESENCE_UPDATED } from '../pubsub'
|
||||
|
||||
const DEBOUNCE_TIME_MS = 10000
|
||||
const RECENT_THRESHOLD_IN_MINUTES = 5
|
||||
const RECENT_THRESHOLD_IN_MINUTES = 3
|
||||
|
||||
let working = false
|
||||
|
||||
|
|
|
@ -116,31 +116,33 @@ const Query = {
|
|||
|
||||
search = search.toLowerCase().trim()
|
||||
|
||||
const userPresences = usersWithWifiDevices
|
||||
.filter(
|
||||
user =>
|
||||
user.displayName.toLowerCase().includes(search) ||
|
||||
user.sAMAccountName.toLowerCase().includes(search) ||
|
||||
user.wifiDevices.some(
|
||||
device =>
|
||||
device.apName.toLowerCase().includes(search) ||
|
||||
device.ip.startsWith(search)
|
||||
)
|
||||
)
|
||||
.map(user => ({
|
||||
user: {
|
||||
id: user.id,
|
||||
displayName: user.displayName,
|
||||
thumbnailPhoto: user.thumbnailPhoto
|
||||
},
|
||||
wifiDevices: user.wifiDevices
|
||||
}))
|
||||
const filteredUsers = search
|
||||
? usersWithWifiDevices.filter(
|
||||
user =>
|
||||
user.displayName.toLowerCase().includes(search) ||
|
||||
user.sAMAccountName.toLowerCase().includes(search) ||
|
||||
user.wifiDevices.some(
|
||||
device =>
|
||||
device.apName.toLowerCase().includes(search) ||
|
||||
device.ip.startsWith(search)
|
||||
)
|
||||
)
|
||||
: usersWithWifiDevices
|
||||
|
||||
const sortedUserPresences = userPresences.sort((a, b) =>
|
||||
const sortedUsers = filteredUsers.sort((a, b) =>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
6
server/src/resolvers/UserPresence.js
Normal file
6
server/src/resolvers/UserPresence.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
const UserPresence = {
|
||||
displayName: _ => (_.displayName ? _.displayName.capitalize() : ''),
|
||||
lastSeen: _ => _.lastSeen?.toISOString()
|
||||
}
|
||||
|
||||
export { UserPresence }
|
|
@ -1,9 +1,6 @@
|
|||
const WifiDevice = {
|
||||
lastSeen: _ => _.lastSeen?.toISOString(),
|
||||
firstSeen: _ => _.firstSeen?.toISOString(),
|
||||
isOnline: _ => _.status == 'ONLINE',
|
||||
isRecent: _ => _.status == 'RECENT',
|
||||
isOffline: _ => _.status == 'OFFLINE'
|
||||
firstSeen: _ => _.firstSeen?.toISOString()
|
||||
}
|
||||
|
||||
export { WifiDevice }
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Mutation } from './Mutation'
|
|||
import { Subscription } from './Subscriptions'
|
||||
|
||||
import { User } from './User'
|
||||
import { UserPresence } from './UserPresence'
|
||||
import { Group } from './Group'
|
||||
import { ResetToken } from './ResetToken'
|
||||
import { WifiDevice } from './WifiDevice'
|
||||
|
@ -13,6 +14,7 @@ const resolvers = {
|
|||
Mutation,
|
||||
Subscription,
|
||||
User,
|
||||
UserPresence,
|
||||
Group,
|
||||
ResetToken,
|
||||
WifiDevice,
|
||||
|
|
|
@ -167,14 +167,15 @@ const typeDefs = gql`
|
|||
uptime: String
|
||||
apName: String
|
||||
status: Status
|
||||
isOnline: Boolean
|
||||
isRecent: Boolean
|
||||
isOffline: Boolean
|
||||
}
|
||||
|
||||
type UserPresence {
|
||||
user: User!
|
||||
wifiDevices: [WifiDevice!]!
|
||||
id: ID!
|
||||
displayName: String!
|
||||
thumbnailPhoto: String
|
||||
lastSeen: String!
|
||||
status: Status!
|
||||
apName: String!
|
||||
}
|
||||
|
||||
enum Status {
|
||||
|
|
|
@ -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>
|
|
@ -3,7 +3,9 @@
|
|||
<v-expansion-panel-header>
|
||||
<v-icon
|
||||
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
|
||||
</v-icon>
|
||||
|
@ -13,7 +15,7 @@
|
|||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-list dense>
|
||||
<v-list-item v-if="wifiDevice.isOnline">
|
||||
<v-list-item v-if="wifiDevice.status == 'ONLINE'">
|
||||
<v-list-item-action>
|
||||
<v-icon color="green darken-1">mdi-wifi</v-icon>
|
||||
</v-list-item-action>
|
||||
|
|
|
@ -42,8 +42,8 @@ export default {
|
|||
lastSeen
|
||||
mac
|
||||
oui
|
||||
isOnline
|
||||
uptime
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,12 +51,113 @@
|
|||
</v-scale-transition>
|
||||
</div>
|
||||
|
||||
<UserPresenceStatusList
|
||||
:user-presences="pagedUserPresences"
|
||||
:loading="
|
||||
$apollo.queries.userPresence.loading && !pagedUserPresences.length
|
||||
<transition-group
|
||||
v-if="
|
||||
!$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-if="!pagedUserPresences.length && search"
|
||||
icon="mdi-account-search"
|
||||
|
@ -124,11 +225,11 @@
|
|||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import UserPresenceStatusList from '../components/UserPresenceStatusList'
|
||||
import Avatar from '../components/Avatar'
|
||||
|
||||
export default {
|
||||
name: 'UserPresence',
|
||||
components: { UserPresenceStatusList },
|
||||
components: { Avatar },
|
||||
data: () => {
|
||||
const pageSize = 12
|
||||
return {
|
||||
|
@ -143,20 +244,12 @@ export default {
|
|||
query: gql`
|
||||
query($search: String = "") {
|
||||
userPresence(search: $search) {
|
||||
user {
|
||||
id
|
||||
displayName
|
||||
thumbnailPhoto
|
||||
}
|
||||
wifiDevices {
|
||||
apName
|
||||
id
|
||||
lastSeen
|
||||
isOnline
|
||||
isRecent
|
||||
isOffline
|
||||
controller
|
||||
}
|
||||
id
|
||||
displayName
|
||||
thumbnailPhoto
|
||||
lastSeen
|
||||
status
|
||||
apName
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
@ -194,8 +287,27 @@ export default {
|
|||
pagedUserPresences() {
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
|
|
@ -33,7 +33,8 @@
|
|||
<div v-if="wifiDevices" class="text-center my-4">
|
||||
<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>
|
||||
online de
|
||||
|
@ -56,7 +57,7 @@
|
|||
<v-row dense align-content="center" no-gutters>
|
||||
<v-col class="shrink" align-self="center">
|
||||
<v-badge
|
||||
:color="device.isOnline ? 'green' : 'grey'"
|
||||
:color="device.status == 'ONLINE' ? 'green' : 'grey'"
|
||||
left
|
||||
offset-y="16"
|
||||
dot
|
||||
|
@ -84,7 +85,7 @@
|
|||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-list dense>
|
||||
<v-list-item v-if="device.isOnline">
|
||||
<v-list-item v-if="device.status == 'ONLINE'">
|
||||
<v-list-item-action>
|
||||
<v-icon color="green darken-1">mdi-wifi</v-icon>
|
||||
</v-list-item-action>
|
||||
|
@ -234,7 +235,7 @@ export default {
|
|||
ip
|
||||
uptime
|
||||
apName
|
||||
isOnline
|
||||
status
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
offset-y="10"
|
||||
:content="
|
||||
user.wifiDevices
|
||||
.filter(wifiDevice => !wifiDevice.isOnline)
|
||||
.filter(wifiDevice => !wifiDevice.status == 'ONLINE')
|
||||
.length.toString()
|
||||
"
|
||||
>
|
||||
|
@ -70,7 +70,7 @@
|
|||
offset-y="10"
|
||||
:content="
|
||||
user.wifiDevices
|
||||
.filter(wifiDevice => wifiDevice.isOnline)
|
||||
.filter(wifiDevice => wifiDevice.status == 'ONLINE')
|
||||
.length.toString()
|
||||
"
|
||||
>
|
||||
|
@ -87,7 +87,10 @@
|
|||
>
|
||||
<v-expansion-panel-header>
|
||||
<div>
|
||||
<v-icon left :color="device.isOnline ? 'green' : ''">
|
||||
<v-icon
|
||||
left
|
||||
:color="device.status == 'ONLINE' ? 'green' : ''"
|
||||
>
|
||||
mdi-cellphone-wireless
|
||||
</v-icon>
|
||||
{{ device.hostname || device.mac }} {{ device.oui }}
|
||||
|
@ -95,7 +98,7 @@
|
|||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-list dense>
|
||||
<v-list-item v-if="device.isOnline">
|
||||
<v-list-item v-if="device.status == 'ONLINE'">
|
||||
<v-list-item-action>
|
||||
<v-icon color="green darken-1">mdi-wifi</v-icon>
|
||||
</v-list-item-action>
|
||||
|
@ -214,7 +217,7 @@ export default {
|
|||
return this.wifiUsers?.map(user => ({
|
||||
...user,
|
||||
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
|
||||
firstSeen
|
||||
lastSeen
|
||||
isOnline
|
||||
status
|
||||
apName
|
||||
essid
|
||||
ip
|
||||
|
|
Loading…
Reference in New Issue
Block a user