User presence improvements

This commit is contained in:
Douglas Barone 2020-11-19 11:18:59 -04:00
parent bfa7ec4573
commit ba56fee068
17 changed files with 348 additions and 64 deletions

View File

@ -0,0 +1,68 @@
# Migration `20201119134248-add-controller-to-wifi-device`
This migration has been generated by Douglas Barone at 11/19/2020, 9:42:48 AM.
You can check out the [state of the schema](./schema.prisma) after the migration.
## Database Steps
```sql
ALTER TABLE "WifiDevice" ADD COLUMN "controller" TEXT NOT NULL DEFAULT E'unknown'
```
## Changes
```diff
diff --git schema.prisma schema.prisma
migration 20201110194349-init..20201119134248-add-controller-to-wifi-device
--- datamodel.dml
+++ datamodel.dml
@@ -3,9 +3,9 @@
}
datasource db {
provider = "postgresql"
- url = "***"
+ url = "***"
}
model ResetToken {
id Int @id @default(autoincrement())
@@ -71,23 +71,24 @@
WifiDevice WifiDevice[] @relation("wifidevice_to_user")
}
model WifiDevice {
- id Int @id @default(autoincrement())
- oui String?
- mac String @unique
- hostname String?
- firstSeen DateTime?
- lastSeen DateTime?
- essid String?
- ip String?
- uptime String?
- apName String?
- status Status?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- userId Int?
- user User? @relation(fields: [userId], references: [id], name: "wifidevice_to_user")
+ id Int @id @default(autoincrement())
+ mac String @unique
+ controller String @default("unknown")
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ oui String?
+ hostname String?
+ firstSeen DateTime?
+ lastSeen DateTime?
+ essid String?
+ ip String?
+ uptime String?
+ apName String?
+ status Status?
+ userId Int?
+ user User? @relation(fields: [userId], references: [id], name: "wifidevice_to_user")
}
enum Status {
ONLINE
```

View File

@ -0,0 +1,96 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = "***"
}
model ResetToken {
id Int @id @default(autoincrement())
token String @unique
expiration DateTime
usedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], name: "resettoken_to_user")
userId Int
creator User @relation(fields: [creatorId], references: [id], name: "resettoken_to_creator")
creatorId Int
}
model User {
id Int @id @default(autoincrement())
lastLogin DateTime?
lastLoginPrior DateTime?
roles Json?
groups Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accountExpires String?
badPasswordTime String?
badPwdCount String?
cn String?
department String?
description String?
displayName String?
distinguishedName String?
dn String?
extensionAttribute1 String?
extensionAttribute10 String?
extensionAttribute2 String?
extensionAttribute6 String?
extensionAttribute7 String?
givenName String?
homeDirectory String?
homeDrive String?
lastLogoff String?
lastLogon String?
lastLogonTimestamp String?
lockoutTime String?
logonCount String?
mail String?
name String?
objectCategory String?
objectGUID String?
objectSid String?
primaryGroupID String?
pwdLastSet DateTime?
sAMAccountName String @unique
sAMAccountType String?
sn String?
thumbnailPhoto String?
title String?
userAccountControl String?
userPrincipalName String?
whenChanged String?
whenCreated String?
createdTokens ResetToken[] @relation("resettoken_to_user")
tokens ResetToken[] @relation("resettoken_to_creator")
WifiDevice WifiDevice[] @relation("wifidevice_to_user")
}
model WifiDevice {
id Int @id @default(autoincrement())
mac String @unique
controller String @default("unknown")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
oui String?
hostname String?
firstSeen DateTime?
lastSeen DateTime?
essid String?
ip String?
uptime String?
apName String?
status Status?
userId Int?
user User? @relation(fields: [userId], references: [id], name: "wifidevice_to_user")
}
enum Status {
ONLINE
OFFLINE
}

View File

@ -0,0 +1,37 @@
{
"version": "0.3.14-fixed",
"steps": [
{
"tag": "CreateField",
"model": "WifiDevice",
"field": "controller",
"type": "String",
"arity": "Required"
},
{
"tag": "CreateDirective",
"location": {
"path": {
"tag": "Field",
"model": "WifiDevice",
"field": "controller"
},
"directive": "default"
}
},
{
"tag": "CreateArgument",
"location": {
"tag": "Directive",
"path": {
"tag": "Field",
"model": "WifiDevice",
"field": "controller"
},
"directive": "default"
},
"argument": "",
"value": "\"unknown\""
}
]
}

View File

@ -1,3 +1,4 @@
# Prisma Migrate lockfile v1
20201110194349-init
20201110194349-init
20201119134248-add-controller-to-wifi-device

View File

@ -72,21 +72,22 @@ model User {
}
model WifiDevice {
id Int @id @default(autoincrement())
oui String?
mac String @unique
hostname String?
firstSeen DateTime?
lastSeen DateTime?
essid String?
ip String?
uptime String?
apName String?
status Status?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int?
user User? @relation(fields: [userId], references: [id], name: "wifidevice_to_user")
id Int @id @default(autoincrement())
mac String @unique
controller String @default("unknown")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
oui String?
hostname String?
firstSeen DateTime?
lastSeen DateTime?
essid String?
ip String?
uptime String?
apName String?
status Status?
userId Int?
user User? @relation(fields: [userId], references: [id], name: "wifidevice_to_user")
}
enum Status {

View File

@ -106,11 +106,9 @@ const Query = {
},
userPresence: async (_, { search }) => {
if (!search) {
//await updateDBWithOnlineDevices()
updateDBWithOnlineDevices()
search = ''
}
if (!search) search = ''
updateDBWithOnlineDevices()
const usersWithWifiDevices = await prisma.user.findMany({
where: {
@ -123,7 +121,9 @@ const Query = {
.filter(
user =>
user.displayName.toLowerCase().includes(search.toLowerCase()) ||
user.WifiDevice[0].apName.toLowerCase().includes(search.toLowerCase())
user.WifiDevice.some(device =>
device.apName.toLowerCase().includes(search.toLowerCase())
)
)
.map(user => ({
user: {

View File

@ -28,7 +28,7 @@ const typeDefs = gql`
identifiedOnly: Boolean = true
): [WifiDevice]!
userPresence(search: String): [UserPresence!] @auth(roles: ["watcher"])
userPresence(search: String = ""): [UserPresence!] @auth(roles: ["watcher"])
}
type Mutation {
@ -140,9 +140,10 @@ const typeDefs = gql`
type WifiDevice {
user: User
id: String
id: ID!
oui: String
mac: String
mac: String!
controller: String!
hostname: String
firstSeen: String
lastSeen: String

View File

@ -61,7 +61,8 @@ export async function getOnlineWifiDevices() {
ip: client.IP,
uptime: client.UT.toString(),
apName: client.AP,
status: client.ST == 'Online' ? 'ONLINE' : 'OFFLINE'
status: client.ST == 'Online' ? 'ONLINE' : 'OFFLINE',
controller: "Cisco"
}))
return hydratedOnlineDevices

View File

@ -188,15 +188,14 @@ export async function getOnlineWifiDevices() {
ip: client.ip,
uptime: client.uptime.toString(),
apName: accessPoints[0].find(ap => ap.mac === client.ap_mac).name,
status: 'ONLINE'
status: 'ONLINE',
controller: "UniFi"
}))
unifiController.logout()
await unifiController.logout()
return hydratedOnlineDevices
} catch (e) {
throw new Error('Error getting devices. ' + e)
}
}

View File

@ -13,41 +13,49 @@ async function updateDBWithOnlineDevices() {
const onlineUnifiDevicesPromise = getOnlineUnifiDevices()
const onlineCiscoDevicesPromise = getOnlineCiscoDevices()
const [onlineUnifiDevices, onlineCiscoDevices] = await Promise.all([
onlineUnifiDevicesPromise,
onlineCiscoDevicesPromise
])
try {
const [onlineUnifiDevices, onlineCiscoDevices] = await Promise.all([
onlineUnifiDevicesPromise,
onlineCiscoDevicesPromise
])
await prisma.wifiDevice.updateMany({
data: {
status: 'OFFLINE'
}
})
await prisma.wifiDevice.updateMany({
data: {
status: 'OFFLINE'
}
})
const onlineDevices = [...onlineUnifiDevices, ...onlineCiscoDevices]
const onlineDevices = [...onlineUnifiDevices, ...onlineCiscoDevices]
for (const onlineDevice of onlineDevices) {
const device = {
...onlineDevice,
user: onlineDevice.user
? { connect: { sAMAccountName: onlineDevice.user } }
: undefined
for (const onlineDevice of onlineDevices) {
const device = {
...onlineDevice,
user: onlineDevice.user
? { connect: { sAMAccountName: onlineDevice.user } }
: undefined
}
try {
await prisma.wifiDevice.upsert({
where: { mac: onlineDevice.mac },
create: device,
update: device
})
} catch (e) {
if (e.code != 'P2016') console.log('[wifiDevice upsert error]', e)
}
}
try {
await prisma.wifiDevice.upsert({
where: { mac: onlineDevice.mac },
create: device,
update: device
})
} catch (e) {
if (e.code != 'P2016') console.log('[wifiDevice upsert error]', e)
}
setTimeout(() => {
updating = false
}, 10000)
// TODO: pubsub
return onlineDevices.length
} catch (e) {
console.log('Error updating DB: ', e)
return -2
}
updating = false
// TODO: pubsub
return onlineDevices.length
}
export { updateDBWithOnlineDevices }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="logo_1_" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 400 300"
style="enable-background:new 0 0 400 300;" xml:space="preserve">
<style type="text/css">
.st0{fill:#049FD9;}
</style>
<path id="logo" transform="translate(-446.85715,-442.59426)" class="st0" d="M836.9,554.6c0-4.6-3.7-8.2-8.2-8.2
c-4.6,0-8.3,3.7-8.3,8.2v17.3c0,4.6,3.7,8.3,8.3,8.3c4.5,0,8.2-3.7,8.2-8.3V554.6z M791.5,531.8c0-4.5-3.7-8.2-8.3-8.2
c-4.6,0-8.2,3.7-8.2,8.2v40.1c0,4.6,3.7,8.3,8.2,8.3c4.7,0,8.3-3.7,8.3-8.3C791.5,571.9,791.5,531.8,791.5,531.8z M746,500.7
c0-4.6-3.7-8.2-8.2-8.2c-4.6,0-8.2,3.7-8.2,8.2v87.7c0,4.6,3.7,8.3,8.2,8.3c4.6,0,8.2-3.7,8.2-8.3C746,588.4,746,500.7,746,500.7z
M700.6,531.8c0-4.5-3.7-8.2-8.2-8.2c-4.5,0-8.2,3.7-8.2,8.2v40.1c0,4.6,3.7,8.3,8.2,8.3c4.6,0,8.2-3.7,8.2-8.3
C700.6,571.9,700.6,531.8,700.6,531.8z M655.1,554.6c0-4.6-3.7-8.2-8.2-8.2c-4.6,0-8.3,3.7-8.3,8.2v17.3c0,4.6,3.7,8.3,8.3,8.3
c4.5,0,8.2-3.7,8.2-8.3C655.1,571.9,655.1,554.6,655.1,554.6z M609.7,531.8c0-4.5-3.7-8.2-8.3-8.2c-4.6,0-8.3,3.7-8.3,8.2v40.1
c0,4.6,3.7,8.3,8.3,8.3c4.6,0,8.3-3.7,8.3-8.3C609.7,571.9,609.7,531.8,609.7,531.8z M564.3,500.7c0-4.6-3.7-8.2-8.3-8.2
c-4.6,0-8.3,3.7-8.3,8.2v87.7c0,4.6,3.7,8.3,8.3,8.3c4.6,0,8.3-3.7,8.3-8.3C564.3,588.4,564.3,500.7,564.3,500.7z M518.8,531.8
c0-4.5-3.7-8.2-8.3-8.2s-8.3,3.7-8.3,8.2v40.1c0,4.6,3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3V531.8z M473.4,554.6c0-4.6-3.7-8.2-8.3-8.2
c-4.6,0-8.3,3.7-8.3,8.2v17.3c0,4.6,3.7,8.3,8.3,8.3c4.6,0,8.3-3.7,8.3-8.3L473.4,554.6L473.4,554.6z M644.2,626.4
c-0.5-0.1-8.1-2.1-16.2-2.1c-15.4,0-24.6,8.3-24.6,20.6c0,10.9,7.7,16.4,17,19.3c1,0.3,2.5,0.8,3.6,1.1c4.1,1.3,7.4,3.2,7.4,6.6
c0,3.7-3.8,6.2-12.1,6.2c-7.3,0-14.3-2.1-15.7-2.5v15.2c0.8,0.2,9.1,1.8,18,1.8c12.8,0,27.3-5.6,27.3-22.2c0-8-4.9-15.5-15.7-18.9
l-4.6-1.5c-2.7-0.9-7.6-2.3-7.6-6.3c0-3.2,3.6-5.4,10.3-5.4c5.8,0,12.8,1.9,13,2V626.4L644.2,626.4z M785,658.5
c0,9.6-7.4,17.4-17.2,17.4c-9.9,0-17.2-7.8-17.2-17.4c0-9.6,7.3-17.4,17.2-17.4C777.6,641.2,785,649,785,658.5 M767.8,624.4
c-20.3,0-34.9,15.3-34.9,34.2c0,18.9,14.6,34.2,34.9,34.2c20.3,0,34.9-15.3,34.9-34.2C802.7,639.7,788.1,624.4,767.8,624.4
M541.4,626.7c-1.6-0.5-7.4-2.4-15.2-2.4c-20.3,0-35.2,14.5-35.2,34.2c0,21.3,16.4,34.2,35.2,34.2c7.4,0,13.1-1.8,15.2-2.4v-17.7
c-0.7,0.4-6.2,3.5-14,3.5c-11.1,0-18.3-7.8-18.3-17.6c0-10.1,7.5-17.6,18.3-17.6c8,0,13.3,3.2,14,3.5V626.7z M715.3,626.7
c-1.6-0.5-7.4-2.4-15.2-2.4c-20.3,0-35.2,14.5-35.2,34.2c0,21.3,16.5,34.2,35.2,34.2c7.4,0,13.1-1.8,15.2-2.4v-17.7
c-0.7,0.4-6.2,3.5-14,3.5c-11.1,0-18.2-7.8-18.2-17.6c0-10.1,7.5-17.6,18.2-17.6c8,0,13.3,3.2,14,3.5V626.7z M581,691.6h-16.7v-66
H581V691.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="720" height="1024" viewBox="0 0 720 1024">
<g id="icomoon-ignore">
</g>
<path fill="#4A4A4A" d="M288.4 419c3.2-19.6 10.4-45.6 26.6-68 21.8-29.8 58.2-55.6 129.4-55.4l7.2-33.4c-83.8-2.6-135.8 29.8-163.6 69.2-21.8 30.6-30 64-33.2 87.4l33.6 0.2zM349 419c2.8-9.8 7.2-20.4 14-29.8 12.4-16.8 31.6-31.6 69-34.6l7.2-34c-51.4 1.4-85 22.8-103.2 49-12 16.8-18 34.4-21.2 49.2zM314.4 419h34.6z"></path>
<path fill="#4A4A4A" d="M417.8 418.8l7.4-34c-20.8 1.6-33 11-41.2 23-3 4.2-4.8 7-6.6 11h40.4zM642.2 418.8h-36l7.6-34c0 0.2 27.8 6.4 28.4 34zM81.4 385l-38.4 181c-6.8 31.8 2.8 46 27.2 46 24 0 40-14.2 46.8-46l38.6-181h40.2l-37.8 177.8c-12.4 58.4-46 79.4-94 79.4-48.4 0-73-21.2-60.6-79.4l37.8-177.8h40.2zM211.6 447.4h36.2l-4.6 22.2h1c13.2-17 33.4-27.2 54.6-27.2 29 0 44.6 13.2 36.4 51.6l-30.4 143.6h-38.2l27.6-130.4c5.2-24.8 0-34.4-19.2-34.4-15.6 0-33 12.4-38.2 37.2l-27.2 127.6h-38l40-190.2zM373.8 447.4h38.2l-40.4 190.2h-38.2l40.4-190.2zM455.4 385h128.8l-7.4 34h-88.4l-14.8 70h88.6l-7 34h-88.6l-24.4 114.6h-40.4l53.6-252.6zM600.4 447.4h38.2l-40.4 190.2h-38.2l40.4-190.2z"></path>
<path fill="#4A4A4A" d="M694.2 438c-15.2 0-26-11.4-26-26.4 0-16 12-26.4 26-26.4 13.8 0 25.8 10.6 25.8 26.4 0.2 15.8-11.8 26.4-25.8 26.4zM694.2 389.6c-11.4 0-20.4 9-20.4 21.8 0 12 7.8 21.8 20.4 21.8 11.4 0 20.2-9 20.2-21.8 0.2-12.8-8.8-21.8-20.2-21.8zM689 426.6h-4.8v-29.2h11c7 0 10.6 2.4 10.6 8.4 0 5.4-3.2 7.4-7.6 8l8.2 12.8h-5.4l-7.6-12.6h-4.6l0.2 12.6zM694.4 410c3.6 0 6.8-0.4 6.8-4.6 0-3.6-3.4-4-6.4-4h-5.8v8.6h5.4z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,26 @@
<template>
<v-avatar size="28px" class="ml-2">
<v-img contain :src="logo" />
</v-avatar>
</template>
<script>
import ciscoLogo from '../assets/cisco_logo.svg'
import unifiLogo from '../assets/unifi_logo.svg'
export default {
props: {
controller: {
type: String,
default: 'unknown'
}
},
computed: {
logo() {
return {
Cisco: ciscoLogo,
UniFi: unifiLogo
}[this.controller]
}
}
}
</script>

View File

@ -43,6 +43,7 @@
<span class="font-weight-medium"
>{{ userPresence.wifiDevices[0].apName }}
</span>
<ApIcon :controller="userPresence.wifiDevices[0].controller" />
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-icon>
@ -96,9 +97,11 @@
<script>
import Avatar from './Avatar'
import ApIcon from './ApIcon'
export default {
name: 'UserPresenceStatusList',
components: { Avatar },
components: { Avatar, ApIcon },
props: {
userPresences: {
type: Array,

View File

@ -5,19 +5,20 @@
<v-text-field
v-model="search"
label="Pesquisar"
hint="Usuário ou Access Point (Ex.: jose, pp, sala 102)"
prepend-inner-icon="mdi-account-search"
outlined
rounded
autofocus
clearable
hide-details
style="max-width: 480px"
/>
<v-btn
:loading="$apollo.queries.userPresence.loading"
color="primary"
class="ml-4"
class="ml-4 mb-8"
icon
large
x-large
@click="$apollo.queries.userPresence.refresh()"
>
<v-icon>mdi-refresh</v-icon>
@ -71,7 +72,7 @@ export default {
fetchPolicy: 'cache-and-network',
pollInterval: 300000, // 5min
query: gql`
query($search: String) {
query($search: String = "") {
userPresence(search: $search) {
user {
id
@ -83,6 +84,7 @@ export default {
id
lastSeen
status
controller
}
}
}