OK... That's a lot of changes...

This commit is contained in:
Douglas Barone 2022-06-14 13:26:01 +00:00
parent 3ad1b6e1c7
commit 69575c69d8
54 changed files with 522 additions and 355 deletions

14
.prettierrc Executable file
View File

@ -0,0 +1,14 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"bracketSpacing": true,
"arrowParens": "avoid",
"overrides": [
{
"files": "*.js, *.vue, *.css, *.scss",
"excludeFiles": "**/dist/**, **/node_modules/**"
}
]
}

View File

@ -1,9 +1,9 @@
module.exports = { module.exports = {
client: { client: {
includes: ["web/src/**/*.{js,jsx,ts,tsx,vue,gql}"], includes: ['web/src/**/*.{js,jsx,ts,tsx,vue,gql}'],
service: { service: {
name: "ifms-pti-srv", name: 'ifms-pti-srv',
url: "http://localhost:4000/graphql", url: 'http://localhost:4000/graphql'
}, }
}, }
}; }

View File

@ -5,5 +5,10 @@
"singleQuote": true, "singleQuote": true,
"bracketSpacing": true, "bracketSpacing": true,
"arrowParens": "avoid", "arrowParens": "avoid",
"excludeFiles": "dist/**" "overrides": [
{
"files": "*.js, *.vue, *.css, *.scss",
"excludeFiles": "**/dist/**, **/node_modules/**"
}
]
} }

View File

@ -1,4 +1,5 @@
# Portal de TI - Server # Portal de TI - Server
Servidor de API GraphQL para o Portal de TI do IFMS Servidor de API GraphQL para o Portal de TI do IFMS
## Requisitos ## Requisitos
@ -8,30 +9,35 @@ Servidor de API GraphQL para o Portal de TI do IFMS
- Docker Compose - Docker Compose
## Desenvolvimento ## Desenvolvimento
~~~
```
cp .env.example .env cp .env.example .env
~~~ ```
Altere as variáveis de ambiente em `.env` Altere as variáveis de ambiente em `.env`
~~~ ```
docker-compose up -d docker-compose up -d
npm run prisma-deploy # Rodar sempre que alterar o schema npm run prisma-deploy # Rodar sempre que alterar o schema
npm run dev npm run dev
~~~ ```
## Compilar para produção ## Compilar para produção
~~~
```
npm run prisma-deploy npm run prisma-deploy
npm run build npm run build
~~~ ```
## Produção ## Produção
Não é recomendado usar o arquivo .env em produção. Não é recomendado usar o arquivo .env em produção.
Configure as variáveis de ambiente no servidor. Configure as variáveis de ambiente no servidor.
~~~ ```
npm start npm start
~~~ ```
--- ---
Desenvolvido pelo SERTI Ponta Porã Desenvolvido pelo SERTI Ponta Porã

View File

@ -0,0 +1,29 @@
/*
Warnings:
- The `uptime` column on the `AccessPoint` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `uptime` column on the `WifiDevice` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- You are about to drop the column `avgSignal` on the `WifiStats` table. All the data in the column will be lost.
- You are about to drop the column `maxSignal` on the `WifiStats` table. All the data in the column will be lost.
- You are about to drop the column `minSignal` on the `WifiStats` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "AccessPoint" ADD COLUMN "usage" BIGINT,
DROP COLUMN "uptime",
ADD COLUMN "uptime" INTEGER;
-- AlterTable
ALTER TABLE "WifiDevice" DROP COLUMN "uptime",
ADD COLUMN "uptime" INTEGER,
ALTER COLUMN "usage" SET DATA TYPE BIGINT;
-- AlterTable
ALTER TABLE "WifiStats" DROP COLUMN "avgSignal",
DROP COLUMN "maxSignal",
DROP COLUMN "minSignal",
ADD COLUMN "avgSignalStrength" INTEGER,
ADD COLUMN "maxSignalStrength" INTEGER,
ADD COLUMN "minSignalStrength" INTEGER,
ALTER COLUMN "avgUsage" SET DATA TYPE BIGINT,
ALTER COLUMN "sumUsage" SET DATA TYPE BIGINT;

View File

@ -89,13 +89,13 @@ model WifiDevice {
notes String? notes String?
essid String? essid String?
uptime String? uptime Int?
apName String? apName String?
signalStrength Int? signalStrength Int?
frequency String? frequency String?
protocol String? protocol String?
speed Int? speed Int?
usage Int? usage BigInt?
status Status? status Status?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@ -160,12 +160,14 @@ model AccessPoint {
notes String? notes String?
inventoryTag String? inventoryTag String?
uptime String? uptime Int?
controller String? controller String?
model String? model String?
ip String? ip String?
clients Int? clients Int?
usage BigInt?
encryptedSshUser String? encryptedSshUser String?
encryptedSshPassword String? encryptedSshPassword String?
@ -182,9 +184,9 @@ model WifiStats {
clients Int? clients Int?
avgSignal Int? avgSignalStrength Int?
minSignal Int? minSignalStrength Int?
maxSignal Int? maxSignalStrength Int?
avgSpeed Int? avgSpeed Int?
minSpeed Int? minSpeed Int?
@ -193,8 +195,8 @@ model WifiStats {
avgClientUptime Int? avgClientUptime Int?
maxClientUptime Int? maxClientUptime Int?
avgUsage Int? avgUsage BigInt?
sumUsage Int? sumUsage BigInt?
accessPointId Int accessPointId Int
accessPoint AccessPoint @relation("wifistats_to_ap", fields: [accessPointId], references: [id]) accessPoint AccessPoint @relation("wifistats_to_ap", fields: [accessPointId], references: [id])

View File

@ -392,8 +392,9 @@ class User {
}) })
logSuccess({ logSuccess({
message: `Importado ${index + 1}/${allAdUsers.length} (${user.sAMAccountName message: `Importado ${index + 1}/${allAdUsers.length} (${
}) ${user.displayName}`, user.sAMAccountName
}) ${user.displayName}`,
data: dbUser data: dbUser
}) })
} }
@ -402,11 +403,12 @@ class User {
logSuccess({ logSuccess({
tags: ['user', 'AD'], tags: ['user', 'AD'],
message: `${allAdUsers.length message: `${
} usuários importados do Active Directory em ${( allAdUsers.length
(endTime - startTime) / } usuários importados do Active Directory em ${(
1000 (endTime - startTime) /
).toFixed(2)}s` 1000
).toFixed(2)}s`
}) })
return allAdUsers.length return allAdUsers.length

View File

@ -5,7 +5,10 @@ import { User } from './classes/User'
import { deleteOldLogs, logInfo, logSuccess } from './lib/logger' import { deleteOldLogs, logInfo, logSuccess } from './lib/logger'
import { updateAccessPoints } from './lib/accessPoints' import { updateAccessPoints } from './lib/accessPoints'
import {
deleteOldStats,
generateStatsForAllAccessPoints
} from './lib/wifiStats'
// WARNING! All crontasks are blocking! Do not await inside it // WARNING! All crontasks are blocking! Do not await inside it
@ -42,3 +45,11 @@ cron.schedule('0 0 2 * * *', () => {
cron.schedule('0 */2 * * * *', () => { cron.schedule('0 */2 * * * *', () => {
updateAccessPoints().catch(console.log) updateAccessPoints().catch(console.log)
}) })
cron.schedule('0 */5 * * * *', () => {
generateStatsForAllAccessPoints()
})
cron.schedule('0 0 2 * * *', () => {
deleteOldStats()
})

View File

@ -1,4 +1,4 @@
import { } from 'dotenv/config' import {} from 'dotenv/config'
import './utils/capitalize' import './utils/capitalize'
import './utils/contains' import './utils/contains'
import './utils/cycle' import './utils/cycle'

View File

@ -1,5 +1,5 @@
import prisma from '../prisma' import prisma from '../prisma'
import { ACCESS_POINTS_UPDATED, pubsub } from '../pubsub'
import { getAccessPoints as getCiscoAccessPoints } from './ciscoController' import { getAccessPoints as getCiscoAccessPoints } from './ciscoController'
import { logInfo, logSuccess } from './logger' import { logInfo, logSuccess } from './logger'
import { getAccessPoints as getUnifiAccessPoints } from './unifiController' import { getAccessPoints as getUnifiAccessPoints } from './unifiController'
@ -50,7 +50,7 @@ export async function updateAccessPoints() {
} }
}, },
data: { data: {
uptime: "-1" uptime: -1
} }
}) })

View File

@ -103,7 +103,7 @@ export async function getOnlineWifiDevices() {
lastSeen: now, lastSeen: now,
essid: client.SSID, essid: client.SSID,
ip: client.IP, ip: client.IP,
uptime: client.UT.toString(), uptime: +client.UT,
apName: client.AP, apName: client.AP,
status: client.ST == 'Online' ? 'ONLINE' : 'OFFLINE', status: client.ST == 'Online' ? 'ONLINE' : 'OFFLINE',
controller: 'Cisco', controller: 'Cisco',
@ -143,14 +143,15 @@ export function getAccessPoints() {
clearTimeout(timeout) clearTimeout(timeout)
const restructuredAccessPoints = accessPoints.map( const restructuredAccessPoints = accessPoints.map(
({ Nm, Mc, Md, Ut, A4, Cl }) => ({ ({ Nm, Mc, Md, Ut, A4, Cl, Vm }) => ({
mac: Mc, mac: Mc,
hostname: Nm, hostname: Nm,
uptime: Ut.toString(), uptime: +Ut,
controller: 'Cisco', controller: 'Cisco',
model: Md, model: Md,
ip: A4, ip: A4,
clients: Cl clients: +Cl,
usage: +Vm
}) })
) )

View File

@ -20,7 +20,8 @@ async function log(
const entryTags = tags.length ? ` [${tags}] ` : '' const entryTags = tags.length ? ` [${tags}] ` : ''
console.log( console.log(
`${color}[${format(now, 'HH:mm:ss')}]${entryTags ? entryTags : '' `${color}[${format(now, 'HH:mm:ss')}]${
entryTags ? entryTags : ''
}\x1b[0m${message}` }\x1b[0m${message}`
) )

View File

@ -105,8 +105,9 @@ async function updateUserIdMappings() {
logSuccess({ logSuccess({
tags: ['paloalto', 'user-id'], tags: ['paloalto', 'user-id'],
message: `Foram mapeados ${devices.length} user-ids em ${pAHost.description || pAHost.cidr message: `Foram mapeados ${devices.length} user-ids em ${
} em ${((endTime - startTime) / 1000).toFixed(2)}s.`, pAHost.description || pAHost.cidr
} em ${((endTime - startTime) / 1000).toFixed(2)}s.`,
data: devices data: devices
}) })
@ -114,8 +115,9 @@ async function updateUserIdMappings() {
} catch (e) { } catch (e) {
logError({ logError({
tags: ['paloalto', 'user-id'], tags: ['paloalto', 'user-id'],
message: `Erro atualizando user-id mappings em ${pAHost.description || pAHost.cidr message: `Erro atualizando user-id mappings em ${
}: ${e.message}` pAHost.description || pAHost.cidr
}: ${e.message}`
}) })
//console.log(e) // Do not add e to log DB for security reasons... //console.log(e) // Do not add e to log DB for security reasons...

View File

@ -187,7 +187,7 @@ export async function getOnlineWifiDevices() {
lastSeen: new Date(client.last_seen * 1000), lastSeen: new Date(client.last_seen * 1000),
essid: client.essid, essid: client.essid,
ip: client.ip, ip: client.ip,
uptime: client.uptime.toString(), uptime: +client.uptime,
apName: accessPoints[0].find(ap => ap.mac === client.ap_mac).name, apName: accessPoints[0].find(ap => ap.mac === client.ap_mac).name,
status: 'ONLINE', status: 'ONLINE',
controller: 'UniFi', controller: 'UniFi',
@ -209,14 +209,15 @@ export async function getAccessPoints() {
const accessPoints = await unifiController.getAccessDevices('default') const accessPoints = await unifiController.getAccessDevices('default')
const restructuredAccessPoints = accessPoints[0].map( const restructuredAccessPoints = accessPoints[0].map(
({ mac, model, ip, uptime, name, num_sta }) => ({ ({ mac, model, ip, uptime, name, num_sta, bytes }) => ({
mac, mac,
hostname: name, hostname: name,
uptime: uptime ? uptime.toString() : "-1", uptime: uptime ? +uptime : -1,
controller: 'UniFi', controller: 'UniFi',
model, model,
ip, ip,
clients: num_sta clients: num_sta,
usage: +bytes
}) })
) )

View File

@ -20,8 +20,9 @@ let working = false
const wifiControllers = [unifiController, ciscoController] const wifiControllers = [unifiController, ciscoController]
async function getOnlineDevices() { async function getOnlineDevices() {
const onlineDevicesPromises = wifiControllers.map(wifiController =>
const onlineDevicesPromises = wifiControllers.map(wifiController => wifiController.getOnlineWifiDevices()) wifiController.getOnlineWifiDevices()
)
const onlineDevices = (await Promise.all(onlineDevicesPromises)).flat() const onlineDevices = (await Promise.all(onlineDevicesPromises)).flat()
@ -86,10 +87,10 @@ async function updateDB(onlineDevices) {
const user = device.user const user = device.user
? { ? {
connect: { connect: {
sAMAccountName: device.user.replace('IFMS\\', '').toLowerCase() sAMAccountName: device.user.replace('IFMS\\', '').toLowerCase()
}
} }
}
: undefined : undefined
const hostname = device.hostname || mockHostName(device) const hostname = device.hostname || mockHostName(device)
@ -184,17 +185,16 @@ function updateDevicesInfo() {
onlineDevices.length > 0 onlineDevices.length > 0
? resolve( ? resolve(
`${onlineDevices.length} atualizados em ${Math.floor( `${onlineDevices.length} atualizados em ${Math.floor(
endTime - startTime endTime - startTime
)}ms, DB em ${Math.floor(endTimeDB - startTimeDB)}ms` )}ms, DB em ${Math.floor(endTimeDB - startTimeDB)}ms`
) )
: reject('Não há dispositivos conectados no momento.') : reject('Não há dispositivos conectados no momento.')
pubsub.publish(USER_PRESENCE_UPDATED, { pubsub.publish(USER_PRESENCE_UPDATED, {
userPresenceUpdated: onlineDevices.length userPresenceUpdated: onlineDevices.length
}) })
const updatedDbAccessPoints = await prisma.accessPoint.findMany({ const updatedDbAccessPoints = await prisma.accessPoint.findMany({
include: { include: {
wifiDevices: { wifiDevices: {
@ -205,12 +205,17 @@ function updateDevicesInfo() {
} }
}) })
pubsub.publish(ACCESS_POINTS_UPDATED, { accessPointsUpdated: updatedDbAccessPoints }) pubsub.publish(ACCESS_POINTS_UPDATED, {
accessPointsUpdated: updatedDbAccessPoints
})
logSuccess({ logSuccess({
tags: ['wifiDevices'], tags: ['wifiDevices'],
message: `${onlineDevices.length} dispositivos Wi-Fi atualizados em message: `${onlineDevices.length} dispositivos Wi-Fi atualizados em
${((endTime - startTime) / 1000).toFixed(2)}s. BD em ${((endTimeDB - startTimeDB) / 1000).toFixed(2)}s` ${((endTime - startTime) / 1000).toFixed(2)}s. BD em ${(
(endTimeDB - startTimeDB) /
1000
).toFixed(2)}s`
}) })
} catch (e) { } catch (e) {
logError({ logError({
@ -229,7 +234,8 @@ function updateDevicesInfo() {
// Delete devices that are offline for more than OLD_DEVICES_THRESHOLD_IN_DAYS days // Delete devices that are offline for more than OLD_DEVICES_THRESHOLD_IN_DAYS days
async function deleteOldDevices() { async function deleteOldDevices() {
const oldDevicesThresholdInMilliseconds = OLD_DEVICES_THRESHOLD_IN_DAYS * 24 * 60 * 60 * 1000 const oldDevicesThresholdInMilliseconds =
OLD_DEVICES_THRESHOLD_IN_DAYS * 24 * 60 * 60 * 1000
const oldDevices = await prisma.wifiDevice.deleteMany({ const oldDevices = await prisma.wifiDevice.deleteMany({
where: { where: {

View File

@ -1,13 +1,17 @@
import prisma from "../prisma"; import prisma from '../prisma'
import { logError, logSuccess } from './logger'
import { subDays } from 'date-fns'
generateStatsForAccessPoint(1) const DAYS_TO_KEEP = 90
async function generateStatsForAccessPoint(accessPointId) { async function generateStatsForAccessPoint(mac) {
const timestamp = new Date() const timestamp = new Date()
const stats = await prisma.wifiDevice.aggregate({ const dbStats = await prisma.wifiDevice.aggregate({
where: { where: {
accessPointId, accessPoint: {
mac
},
status: 'ONLINE' status: 'ONLINE'
}, },
_count: { _count: {
@ -16,41 +20,84 @@ async function generateStatsForAccessPoint(accessPointId) {
_avg: { _avg: {
signalStrength: true, signalStrength: true,
speed: true, speed: true,
usage: true usage: true,
uptime: true
}, },
_max: { _max: {
signalStrength: true, signalStrength: true,
speed: true, speed: true,
usage: true usage: true,
uptime: true
}, },
_min: { _min: {
signalStrength: true, signalStrength: true,
speed: true, speed: true
},
_sum: {
usage: true
} }
}) })
console.log(timestamp, stats) const stats = {
timestamp: timestamp.toISOString(),
clients: dbStats._count._all,
avgSignalStrength: Math.floor(dbStats._avg.signalStrength, 0),
minSignalStrength: dbStats._min.signalStrength,
maxSignalStrength: dbStats._max.signalStrength,
avgSpeed: Math.floor(dbStats._avg.speed, 0),
minSpeed: dbStats._min.speed || 0,
maxSpeed: dbStats._max.speed || 0,
avgClientUptime: Math.floor(dbStats._avg.uptime, 0),
maxClientUptime: dbStats._max.uptime || 0,
avgUsage: Math.floor(dbStats._avg.usage, 0),
sumUsage: dbStats._sum.usage || 0
}
await prisma.wifiStats.create({
data: {
...stats,
accessPoint: { connect: { mac } }
}
})
} }
// id Int @id @default(autoincrement()) export async function generateStatsForAllAccessPoints() {
// timestamp DateTime @default(now()) try {
const accessPoints = await prisma.accessPoint.findMany()
// clients Int? for (const accessPoint of accessPoints) {
await generateStatsForAccessPoint(accessPoint.mac)
}
// avgSignal Int? logSuccess({
// minSignal Int? tags: ['wifiStats', 'generateStatsForAllAccessPoints'],
// maxSignal Int? message: `Estatísticas geradas para ${accessPoints.length} access points`
})
} catch (e) {
console.log(e)
logError({
tags: ['wifiStats', 'generateStatsForAllAccessPoints'],
message: 'Erro ao gerar estatísticas para todos os access points',
data: e
})
}
}
// avgSpeed Int? export async function deleteOldStats() {
// minSpeed Int? try {
// maxSpeed Int? const stats = await prisma.wifiStats.deleteMany({
where: { timestamp: { lt: subDays(new Date(), DAYS_TO_KEEP) } }
})
// avgClientUptime Int? logSuccess({
// maxClientUptime Int? tags: ['wifiStats', 'deleteOldStats'],
message: `${stats.count} estatísticas com mais de ${DAYS_TO_KEEP} dias deletadas.`
// avgUsage Int? })
// sumUsage Int? } catch (e) {
console.log(e)
// accessPointId Int }
// accessPoint AccessPoint @relation("wifistats_to_ap", fields: [accessPointId], references: [id]) }

View File

@ -1,11 +1,10 @@
import prisma from "../prisma"; import prisma from '../prisma'
import { getSubnetInfo } from "../utils/subnetInfo"; import { getSubnetInfo } from '../utils/subnetInfo'
export const AccessPoint = { export const AccessPoint = {
updatedAt: (parent, data, context, info) => parent.updatedAt?.toISOString(), updatedAt: (parent, data, context, info) => parent.updatedAt?.toISOString(),
async clients(parent, data, context, info) { async clients(parent, data, context, info) {
const clientsCount = await prisma.wifiDevice.count({ const clientsCount = await prisma.wifiDevice.count({
where: { where: {
status: 'ONLINE', status: 'ONLINE',
@ -15,8 +14,9 @@ export const AccessPoint = {
} }
}) })
return clientsCount; return clientsCount
}, },
subnetInfo: parent => getSubnetInfo(parent.ip) subnetInfo: parent => getSubnetInfo(parent.ip),
usage: (parent, data, context, info) => parent.usage.toString()
} }

View File

@ -11,7 +11,6 @@ import { delPAHost } from './delPAHost'
import { updateAccessPoint } from './updateAccessPoint' import { updateAccessPoint } from './updateAccessPoint'
const Mutation = { const Mutation = {
login, login,
updatePassword, updatePassword,
replacePassword, replacePassword,

View File

@ -15,8 +15,7 @@ export async function replaceStudentPassword(parent, { data }, { auth }) {
if (isServant) throw new Error(`Usuário ${data.username} é um servidor`) if (isServant) throw new Error(`Usuário ${data.username} é um servidor`)
if (!isStudent) if (!isStudent) throw new Error(`Usuário ${data.username} não é um estudante`)
throw new Error(`Usuário ${data.username} não é um estudante`)
logInfo({ logInfo({
tags: ['replaceStudentPassword', 'user'], tags: ['replaceStudentPassword', 'user'],

View File

@ -1,22 +1,37 @@
import prisma from '../../prisma' import prisma from '../../prisma'
import { ACCESS_POINTS_UPDATED, pubsub } from '../../pubsub' import { ACCESS_POINTS_UPDATED, pubsub } from '../../pubsub'
import { logError, logInfo } from '../../lib/logger' import { logError, logInfo } from '../../lib/logger'
import { getSubnetInfo } from '../../utils/subnetInfo' import { getSubnetInfo } from '../../utils/subnetInfo'
export async function updateAccessPoint(_, { data: { id, name, local, notes } }, { auth },) { export async function updateAccessPoint(
const accessPoint = await prisma.accessPoint.findUnique({ where: { id: parseInt(id) }, }) _,
{ data: { id, name, local, notes } },
{ auth }
) {
const accessPoint = await prisma.accessPoint.findUnique({
where: { id: parseInt(id) }
})
if (!accessPoint) throw new Error('Access Point não encontrado') if (!accessPoint) throw new Error('Access Point não encontrado')
if (getSubnetInfo(accessPoint.ip).shortName !== auth.campus) { if (getSubnetInfo(accessPoint.ip).shortName !== auth.campus) {
logError({ logError({
tags: ['accessPointEdited', 'accessPoints'], tags: ['accessPointEdited', 'accessPoints'],
message: `O usuário ${auth.displayName} (${auth.sAMAccountName}) tentou atualizar as informações do message: `O usuário ${auth.displayName} (${
AP ${accessPoint.name || accessPoint.hostname}, mas não tinha permissão.` auth.sAMAccountName
}) tentou atualizar as informações do
AP ${
accessPoint.name || accessPoint.hostname
}, mas não tinha permissão.`
}) })
throw new Error(`O AP ${accessPoint.name || accessPoint.hostname} não está na rede do campus ${auth.campus}. Você só pode editar APs da rede do seu campus.`) throw new Error(
`O AP ${
accessPoint.name || accessPoint.hostname
} não está na rede do campus ${
auth.campus
}. Você pode editar APs da rede do seu campus.`
)
} }
const updatedAccessPoint = await prisma.accessPoint.update({ const updatedAccessPoint = await prisma.accessPoint.update({
@ -26,7 +41,11 @@ export async function updateAccessPoint(_, { data: { id, name, local, notes } },
logInfo({ logInfo({
tags: ['accessPointEdited', 'accessPoints'], tags: ['accessPointEdited', 'accessPoints'],
message: `O usuário ${auth.displayName} (${auth.sAMAccountName}) atualizou as informações do AP ${updatedAccessPoint.name || updatedAccessPoint.hostname}`, message: `O usuário ${auth.displayName} (${
auth.sAMAccountName
}) atualizou as informações do AP ${
updatedAccessPoint.name || updatedAccessPoint.hostname
}`,
data: updatedAccessPoint data: updatedAccessPoint
}) })

View File

@ -16,9 +16,9 @@ export async function logs(parent, { search, dateIn, dateOut, limit = 100 }) {
}, },
OR: search OR: search
? [ ? [
{ message: { contains: search, mode: 'insensitive' } }, { message: { contains: search, mode: 'insensitive' } },
{ tags: { contains: search, mode: 'insensitive' } } { tags: { contains: search, mode: 'insensitive' } }
] ]
: undefined : undefined
}, },
orderBy: { timestamp: 'desc' }, orderBy: { timestamp: 'desc' },

View File

@ -18,7 +18,9 @@ export async function userPresence(_, { search, onlyServants }) {
if (onlyServants) if (onlyServants)
usersWithWifiDevices = usersWithWifiDevices.filter( usersWithWifiDevices = usersWithWifiDevices.filter(
({ extensionAttribute2 }) => extensionAttribute2 == 'Técnico-administrativo' || extensionAttribute2 == 'Docente' ({ extensionAttribute2 }) =>
extensionAttribute2 == 'Técnico-administrativo' ||
extensionAttribute2 == 'Docente'
) )
let filteredUsers = usersWithWifiDevices let filteredUsers = usersWithWifiDevices
@ -28,7 +30,6 @@ export async function userPresence(_, { search, onlyServants }) {
const searchTerms = search.split(' ') const searchTerms = search.split(' ')
for (const term of searchTerms) { for (const term of searchTerms) {
filteredUsers = filteredUsers.filter( filteredUsers = filteredUsers.filter(
user => user =>
Object.keys(user).some( Object.keys(user).some(
@ -37,14 +38,12 @@ export async function userPresence(_, { search, onlyServants }) {
user.wifiDevices?.some( user.wifiDevices?.some(
device => device =>
device.status != 'OFFLINE' && device.status != 'OFFLINE' &&
( (device.ip?.startsWith(term) ||
device.ip?.startsWith(term) ||
device.apName?.contains(term) || device.apName?.contains(term) ||
device.essid?.contains(term) || device.essid?.contains(term) ||
device.hostname?.contains(term) || device.hostname?.contains(term) ||
device.accessPoint?.name?.contains(term) || device.accessPoint?.name?.contains(term) ||
device.accessPoint?.local?.contains(term) device.accessPoint?.local?.contains(term))
)
) )
) )
} }
@ -80,9 +79,13 @@ export async function userPresence(_, { search, onlyServants }) {
thumbnailPhoto: userPresence.thumbnailPhoto, thumbnailPhoto: userPresence.thumbnailPhoto,
lastSeen: userPresence.wifiDevices[0].lastSeen, lastSeen: userPresence.wifiDevices[0].lastSeen,
status: userPresence.wifiDevices[0].status, status: userPresence.wifiDevices[0].status,
apName: userPresence.wifiDevices[0].accessPoint?.name || userPresence.wifiDevices[0].apName || userPresence.wifiDevices[0].accessPoint?.hostname, apName:
userPresence.wifiDevices[0].accessPoint?.name ||
userPresence.wifiDevices[0].apName ||
userPresence.wifiDevices[0].accessPoint?.hostname,
local: userPresence.wifiDevices[0].accessPoint?.local, local: userPresence.wifiDevices[0].accessPoint?.local,
campus: getSubnetInfo(userPresence.wifiDevices[0].accessPoint?.ip).shortName campus: getSubnetInfo(userPresence.wifiDevices[0].accessPoint?.ip)
.shortName
})) }))
.slice(0, 200) .slice(0, 200)

View File

@ -1,6 +1,9 @@
import prisma from '../../prisma' import prisma from '../../prisma'
export async function wifiDevices(parent, { take = 50, skip = 0, search, sortBy, sortDesc, onlineOnly }) { export async function wifiDevices(
parent,
{ take = 50, skip = 0, search, sortBy, sortDesc, onlineOnly }
) {
const mode = 'insensitive' const mode = 'insensitive'
if (!search) search = '' if (!search) search = ''
@ -28,9 +31,7 @@ export async function wifiDevices(parent, { take = 50, skip = 0, search, sortBy,
}), }),
data: prisma.wifiDevice.findMany({ data: prisma.wifiDevice.findMany({
where, where,
orderBy: [ orderBy: [{ [sortBy || 'hostname']: sortDesc ? 'desc' : 'asc' }],
{ [sortBy || 'hostname']: sortDesc ? 'desc' : 'asc' },
],
include: { user: true, accessPoint: true }, include: { user: true, accessPoint: true },
take, take,
skip skip

View File

@ -4,8 +4,7 @@ import prisma from '../../prisma'
export async function wifiUsers(parent, { take = 10, skip = 0, search }) { export async function wifiUsers(parent, { take = 10, skip = 0, search }) {
const mode = 'insensitive' const mode = 'insensitive'
if (search === null) if (search === null) search = undefined
search = undefined
const where = { const where = {
AND: [ AND: [
@ -19,7 +18,7 @@ export async function wifiUsers(parent, { take = 10, skip = 0, search }) {
accessPoint: { accessPoint: {
OR: [ OR: [
{ name: { contains: search, mode } }, { name: { contains: search, mode } },
{ local: { contains: search, mode } }, { local: { contains: search, mode } }
] ]
} }
} }
@ -27,27 +26,24 @@ export async function wifiUsers(parent, { take = 10, skip = 0, search }) {
}, },
{ wifiDevices: { some: { mac: { contains: search, mode } } } }, { wifiDevices: { some: { mac: { contains: search, mode } } } },
{ wifiDevices: { some: { ip: { contains: search, mode } } } }, { wifiDevices: { some: { ip: { contains: search, mode } } } },
{ displayName: { contains: search, mode } }, , { displayName: { contains: search, mode } },
{ sAMAccountName: { contains: search, mode } }, ,
{ sAMAccountName: { contains: search, mode } }
] ]
} }
] ]
} }
return { return {
data: data: prisma.user.findMany({
prisma.user.findMany({ where,
where, include: {
include: { wifiDevices: true
wifiDevices: true },
}, orderBy: [{ wifiDevices: { _count: 'desc' } }, { displayName: 'asc' }],
orderBy: [ take,
{ wifiDevices: { _count: 'desc' } }, skip
{ displayName: 'asc' }], }),
take,
skip
}),
total: prisma.user.count({ where }) total: prisma.user.count({ where })
} }

View File

@ -20,15 +20,15 @@ const User = {
sharedFolders: parent => sharedFolders: parent =>
parent.groups parent.groups
? parent.groups ? parent.groups
.filter(group => group.cn.includes('-Share-')) .filter(group => group.cn.includes('-Share-'))
.map(group => group.cn.split('-')[2]) .map(group => group.cn.split('-')[2])
: [], : [],
sharedPrinters: parent => sharedPrinters: parent =>
parent.groups parent.groups
? parent.groups ? parent.groups
.filter(group => group.cn.includes('-Printer-')) .filter(group => group.cn.includes('-Printer-'))
.map(group => group.cn.split('-')[2]) .map(group => group.cn.split('-')[2])
: [], : [],
isSuperAdmin: parent => parent.roles.includes('superAdmin'), isSuperAdmin: parent => parent.roles.includes('superAdmin'),
@ -62,24 +62,22 @@ const User = {
return campus || '--' return campus || '--'
}, },
onlineWifiDevicesCount: (parent, data, { auth }) => prisma.wifiDevice.count({ onlineWifiDevicesCount: (parent, data, { auth }) =>
where: { prisma.wifiDevice.count({
status: 'ONLINE', where: {
user: { id: parent.id } status: 'ONLINE',
} user: { id: parent.id }
}), }
}),
offlineWifiDevicesCount: (parent, data, { auth }) => prisma.wifiDevice.count({ offlineWifiDevicesCount: (parent, data, { auth }) =>
where: { prisma.wifiDevice.count({
OR: [ where: {
{ status: 'OFFLINE' }, OR: [{ status: 'OFFLINE' }, { status: 'RECENT' }],
{ status: 'RECENT' },
],
user: { id: parent.id }
}
})
user: { id: parent.id }
}
})
} }
export { User } export { User }

View File

@ -15,25 +15,22 @@ const WifiDevice = {
}) })
).user, ).user,
usage: parent => parent.usage < 0 ? parent.usage * -1 : parent.usage, usage: parent => parent.usage.toString(),
accessPoint: async parent => { accessPoint: async parent => {
try { try {
const ap = await prisma.accessPoint.findUnique({ where: { hostname: parent.apName } }) const ap = await prisma.accessPoint.findUnique({
if (ap) where: { hostname: parent.apName }
return ap })
else if (ap) return ap
return null else return null
} } catch (e) {
catch (e) {
logError({ logError({
tags: ['wifiDevice', 'accessPoint'], tags: ['wifiDevice', 'accessPoint'],
message: `Could not find access point ${parent.apName}`, message: `Could not find access point ${parent.apName}`,
data: parent data: parent
}) })
} }
} }
} }

View File

@ -25,7 +25,7 @@ class AuthDirective extends SchemaDirectiveVisitor {
context.auth = { context.auth = {
...user, ...user,
campus: user.extensionAttribute1?.split('-')[0] || '--', campus: user.extensionAttribute1?.split('-')[0] || '--'
} }
if (user.pwdLastSet.toISOString() === pwdLastSet) { if (user.pwdLastSet.toISOString() === pwdLastSet) {

View File

@ -18,8 +18,7 @@ const typeDefs = gql`
): [User!] @auth(roles: ["servant"]) ): [User!] @auth(roles: ["servant"])
"A single user" "A single user"
user(sAMAccountName: String!): User! user(sAMAccountName: String!): User! @auth(roles: ["superAdmin"])
@auth(roles: ["superAdmin"])
"AD groups" "AD groups"
groups(where: GroupWhereInput!, limit: Int = 10): [Group!]! groups(where: GroupWhereInput!, limit: Int = 10): [Group!]!
@ -29,7 +28,10 @@ const typeDefs = gql`
stats: Stats! stats: Stats!
"Users who has some device currently connected to Wi-Fi" "Users who has some device currently connected to Wi-Fi"
userPresence(search: String = "", onlyServants: Boolean = false): [UserPresence!] @auth(roles: ["watcher"]) userPresence(
search: String = ""
onlyServants: Boolean = false
): [UserPresence!] @auth(roles: ["watcher"])
"Devices that uses the Wi-Fi" "Devices that uses the Wi-Fi"
wifiDevices( wifiDevices(
@ -37,15 +39,13 @@ const typeDefs = gql`
take: Int take: Int
skip: Int skip: Int
sortBy: WifiDevicesResultSortBy = "signalStrength" sortBy: WifiDevicesResultSortBy = "signalStrength"
sortDesc: Boolean = false, sortDesc: Boolean = false
onlineOnly: Boolean = false onlineOnly: Boolean = false
): WifiDevicesResult! @auth(roles: ["superAdmin"]) ): WifiDevicesResult! @auth(roles: ["superAdmin"])
"Users that uses the Wi-Fi" "Users that uses the Wi-Fi"
wifiUsers( wifiUsers(search: String = "", take: Int, skip: Int): WifiUsersResult!
search: String = "" @auth(roles: ["superAdmin"])
take: Int
skip: Int): WifiUsersResult! @auth(roles: ["superAdmin"])
"Application Logs" "Application Logs"
logs( logs(
@ -239,7 +239,7 @@ const typeDefs = gql`
lastSeen: String lastSeen: String
essid: String essid: String
ip: String ip: String
uptime: String uptime: Int
apName: String apName: String
status: Status status: Status
accessPoint: AccessPoint accessPoint: AccessPoint
@ -247,7 +247,7 @@ const typeDefs = gql`
frequency: String frequency: String
protocol: String protocol: String
speed: Int speed: Int
usage: Int usage: String
} }
"A user that is on the Wi-Fi network reach" "A user that is on the Wi-Fi network reach"
@ -322,6 +322,7 @@ const typeDefs = gql`
ip: String ip: String
clients: Int clients: Int
subnetInfo: SubnetInfo subnetInfo: SubnetInfo
usage: String
createdAt: String createdAt: String
updatedAt: String updatedAt: String
@ -405,7 +406,7 @@ const typeDefs = gql`
notes: String notes: String
} }
enum WifiDevicesResultSortBy{ enum WifiDevicesResultSortBy {
mac mac
hostname hostname
firstSeen firstSeen

View File

@ -1,4 +1,4 @@
String.prototype.capitalize = function(lower = true) { String.prototype.capitalize = function (lower = true) {
return (lower ? this.toLowerCase() : this).replace(/(?:^|\s)\S/g, a => return (lower ? this.toLowerCase() : this).replace(/(?:^|\s)\S/g, a =>
a.toUpperCase() a.toUpperCase()
) )

View File

@ -22,9 +22,9 @@
retrocycle, set, stringify, test retrocycle, set, stringify, test
*/ */
if (typeof JSON.decycle !== "function") { if (typeof JSON.decycle !== 'function') {
JSON.decycle = function decycle(object, replacer) { JSON.decycle = function decycle(object, replacer) {
"use strict"; 'use strict'
// Make a deep copy of an object or array, assuring that there is at most // Make a deep copy of an object or array, assuring that there is at most
// one instance of each object or array in the resulting structure. The // one instance of each object or array in the resulting structure. The
@ -50,77 +50,73 @@ if (typeof JSON.decycle !== "function") {
// the object or array. [NUMBER] or [STRING] indicates a child element or // the object or array. [NUMBER] or [STRING] indicates a child element or
// property. // property.
var objects = new WeakMap(); // object to path mappings var objects = new WeakMap() // object to path mappings
return (function derez(value, path) { return (function derez(value, path) {
// The derez function recurses through the object, producing the deep copy. // The derez function recurses through the object, producing the deep copy.
var old_path; // The path of an earlier occurance of value var old_path // The path of an earlier occurance of value
var nu; // The new object or array var nu // The new object or array
// If a replacer function was provided, then call it to get a replacement value. // If a replacer function was provided, then call it to get a replacement value.
if (replacer !== undefined) { if (replacer !== undefined) {
value = replacer(value); value = replacer(value)
} }
// typeof null === "object", so go on if this value is really an object but not // typeof null === "object", so go on if this value is really an object but not
// one of the weird builtin objects. // one of the weird builtin objects.
if ( if (
typeof value === "object" typeof value === 'object' &&
&& value !== null value !== null &&
&& !(value instanceof Boolean) !(value instanceof Boolean) &&
&& !(value instanceof Date) !(value instanceof Date) &&
&& !(value instanceof Number) !(value instanceof Number) &&
&& !(value instanceof RegExp) !(value instanceof RegExp) &&
&& !(value instanceof String) !(value instanceof String)
) { ) {
// If the value is an object or array, look to see if we have already // If the value is an object or array, look to see if we have already
// encountered it. If so, return a {"$ref":PATH} object. This uses an // encountered it. If so, return a {"$ref":PATH} object. This uses an
// ES6 WeakMap. // ES6 WeakMap.
old_path = objects.get(value); old_path = objects.get(value)
if (old_path !== undefined) { if (old_path !== undefined) {
return { $ref: old_path }; return { $ref: old_path }
} }
// Otherwise, accumulate the unique value and its path. // Otherwise, accumulate the unique value and its path.
objects.set(value, path); objects.set(value, path)
// If it is an array, replicate the array. // If it is an array, replicate the array.
if (Array.isArray(value)) { if (Array.isArray(value)) {
nu = []; nu = []
value.forEach(function (element, i) { value.forEach(function (element, i) {
nu[i] = derez(element, path + "[" + i + "]"); nu[i] = derez(element, path + '[' + i + ']')
}); })
} else { } else {
// If it is an object, replicate the object. // If it is an object, replicate the object.
nu = {}; nu = {}
Object.keys(value).forEach(function (name) { Object.keys(value).forEach(function (name) {
nu[name] = derez( nu[name] = derez(
value[name], value[name],
path + "[" + JSON.stringify(name) + "]" path + '[' + JSON.stringify(name) + ']'
); )
}); })
} }
return nu; return nu
} }
return value; return value
}(object, "$")); })(object, '$')
}; }
} }
if (typeof JSON.retrocycle !== 'function') {
if (typeof JSON.retrocycle !== "function") {
JSON.retrocycle = function retrocycle($) { JSON.retrocycle = function retrocycle($) {
"use strict"; 'use strict'
// Restore an object that was reduced by decycle. Members whose values are // Restore an object that was reduced by decycle. Members whose values are
// objects of the form // objects of the form
@ -141,42 +137,42 @@ if (typeof JSON.retrocycle !== "function") {
// return JSON.retrocycle(JSON.parse(s)); // return JSON.retrocycle(JSON.parse(s));
// produces an array containing a single element which is the array itself. // produces an array containing a single element which is the array itself.
var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/; var px =
/^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/
(function rez(value) {
;(function rez(value) {
// The rez function walks recursively through the object looking for $ref // The rez function walks recursively through the object looking for $ref
// properties. When it finds one that has a value that is a path, then it // properties. When it finds one that has a value that is a path, then it
// replaces the $ref object with a reference to the value that is found by // replaces the $ref object with a reference to the value that is found by
// the path. // the path.
if (value && typeof value === "object") { if (value && typeof value === 'object') {
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach(function (element, i) { value.forEach(function (element, i) {
if (typeof element === "object" && element !== null) { if (typeof element === 'object' && element !== null) {
var path = element.$ref; var path = element.$ref
if (typeof path === "string" && px.test(path)) { if (typeof path === 'string' && px.test(path)) {
value[i] = eval(path); value[i] = eval(path)
} else { } else {
rez(element); rez(element)
} }
} }
}); })
} else { } else {
Object.keys(value).forEach(function (name) { Object.keys(value).forEach(function (name) {
var item = value[name]; var item = value[name]
if (typeof item === "object" && item !== null) { if (typeof item === 'object' && item !== null) {
var path = item.$ref; var path = item.$ref
if (typeof path === "string" && px.test(path)) { if (typeof path === 'string' && px.test(path)) {
value[name] = eval(path); value[name] = eval(path)
} else { } else {
rez(item); rez(item)
} }
} }
}); })
} }
} }
}($)); })($)
return $; return $
}; }
} }

View File

@ -3,7 +3,7 @@ import fs from 'fs'
function saveJSONToFile(json, fileName = 'output.json') { function saveJSONToFile(json, fileName = 'output.json') {
const jsonContent = JSON.stringify(json) const jsonContent = JSON.stringify(json)
fs.writeFile(fileName, jsonContent, 'utf8', function(err) { fs.writeFile(fileName, jsonContent, 'utf8', function (err) {
if (err) { if (err) {
console.log('An error occured while writing JSON Object to File.') console.log('An error occured while writing JSON Object to File.')
return console.log(err) return console.log(err)

View File

@ -5,47 +5,37 @@ const subNetsInfo = [
{ {
shortName: 'RT', shortName: 'RT',
name: 'Reitoria', name: 'Reitoria',
cidr: '10.0.0.0/16', cidr: '10.0.0.0/16'
}, },
{ {
shortName: 'RT', shortName: 'RT',
name: 'Reitoria', name: 'Reitoria',
cidr: '10.1.0.0/16', cidr: '10.1.0.0/16',
addresses: [ addresses: [{ name: 'RNP', ip: '200.19.32.4' }]
{ name: 'RNP', ip: '200.19.32.4' }
]
}, },
{ {
shortName: 'AQ', shortName: 'AQ',
name: 'Aquidauana', name: 'Aquidauana',
cidr: '10.2.0.0/16', cidr: '10.2.0.0/16',
addresses: [ addresses: [{ name: 'RNP', ip: '200.19.32.253' }]
{ name: 'RNP', ip: '200.19.32.253' }
]
}, },
{ {
shortName: 'CG', shortName: 'CG',
name: 'Campo Grande', name: 'Campo Grande',
cidr: '10.3.0.0/16', cidr: '10.3.0.0/16',
addresses: [ addresses: [{ name: 'RNP', ip: '200.19.32.120' }]
{ name: 'RNP', ip: '200.19.32.120' }
]
}, },
{ {
shortName: 'CB', shortName: 'CB',
name: 'Corumbá', name: 'Corumbá',
cidr: '10.4.0.0/16', cidr: '10.4.0.0/16',
addresses: [ addresses: [{ name: 'RNP', ip: '200.19.37.2' }]
{ name: 'RNP', ip: '200.19.37.2' }
]
}, },
{ {
shortName: 'CX', shortName: 'CX',
name: 'Coxim', name: 'Coxim',
cidr: '10.5.0.0/16', cidr: '10.5.0.0/16',
addresses: [ addresses: [{ name: 'RNP', ip: '200.19.36.16' }]
{ name: 'RNP', ip: '200.19.36.16' }
],
}, },
{ {
shortName: 'NA', shortName: 'NA',
@ -57,17 +47,13 @@ const subNetsInfo = [
shortName: 'PP', shortName: 'PP',
name: 'Ponta Porã', name: 'Ponta Porã',
cidr: '10.7.0.0/16', cidr: '10.7.0.0/16',
addresses: [ addresses: [{ name: 'RNP', ip: '200.19.34.254' }]
{ name: 'RNP', ip: '200.19.34.254' }
]
}, },
{ {
shortName: 'TL', shortName: 'TL',
name: 'Três Lagoas', name: 'Três Lagoas',
cidr: '10.8.0.0/16', cidr: '10.8.0.0/16',
addresses: [ addresses: [{ name: 'RNP', ip: '200.19.35.2' }]
{ name: 'RNP', ip: '200.19.35.2' }
]
}, },
{ {
shortName: 'JD', shortName: 'JD',
@ -80,13 +66,11 @@ const subNetsInfo = [
name: 'Naviraí', name: 'Naviraí',
cidr: '10.10.0.0/16', cidr: '10.10.0.0/16',
addresses: [] addresses: []
}, },
{ {
shortName: 'DR', shortName: 'DR',
name: 'Dourados', name: 'Dourados',
cidr: '10.11.0.0/16' cidr: '10.11.0.0/16',
,
addresses: [] addresses: []
} }
] ]
@ -103,7 +87,6 @@ export function getSubnetInfo(ip) {
name: 'Sem rede' name: 'Sem rede'
} }
const subnet = subNets.find(subnet => subnet.contains(ip)) const subnet = subNets.find(subnet => subnet.contains(ip))
if (!subnet) if (!subnet)

View File

@ -6,5 +6,10 @@
"bracketSpacing": true, "bracketSpacing": true,
"arrowParens": "avoid", "arrowParens": "avoid",
"vueIndentScriptAndStyle": false, "vueIndentScriptAndStyle": false,
"excludeFiles": "dist/**" "overrides": [
{
"files": "*.js, *.vue, *.css, *.scss",
"excludeFiles": "**/dist/**, **/node_modules/**"
}
]
} }

View File

@ -1,6 +1,7 @@
# Portal de TI - Web Client # Portal de TI - Web Client
## Desenvolvimento ## Desenvolvimento
``` ```
npm install npm install
npm get-schema npm get-schema
@ -8,11 +9,13 @@ npm run serve
``` ```
### Compilar para produção ### Compilar para produção
``` ```
npm run build npm run build
``` ```
### Lint ### Lint
``` ```
npm run lint npm run lint
``` ```

View File

@ -3,7 +3,10 @@
directive @auth(roles: [String!]) on FIELD_DEFINITION directive @auth(roles: [String!]) on FIELD_DEFINITION
directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT | INTERFACE directive @cacheControl(
maxAge: Int
scope: CacheControlScope
) on FIELD_DEFINITION | OBJECT | INTERFACE
type AuthPayload { type AuthPayload {
user: User! user: User!
@ -48,14 +51,23 @@ type Mutation {
} }
type Query { type Query {
"""Returns only a few fields of User""" """
Returns only a few fields of User
"""
basicUser(sAMAccountName: String!): User! basicUser(sAMAccountName: String!): User!
me: User! me: User!
users(where: UserWhereInput!, limit: Int = 15, onlyStudents: Boolean = false): [User!] users(
where: UserWhereInput!
limit: Int = 15
onlyStudents: Boolean = false
): [User!]
user(sAMAccountName: String!): User! user(sAMAccountName: String!): User!
groups(where: GroupWhereInput!, limit: Int = 10): [Group!]! groups(where: GroupWhereInput!, limit: Int = 10): [Group!]!
stats: Stats! stats: Stats!
wifiDevices(search: String = "", identifiedOnly: Boolean = true): [WifiDevice]! wifiDevices(
search: String = ""
identifiedOnly: Boolean = true
): [WifiDevice]!
userPresence(search: String): [UserPresence!] userPresence(search: String): [UserPresence!]
} }
@ -91,10 +103,14 @@ input UpdatePasswordInput {
newPassword: String! newPassword: String!
} }
"""The `Upload` scalar type represents a file upload.""" """
The `Upload` scalar type represents a file upload.
"""
scalar Upload scalar Upload
"""A mix between the database User and the Active Directory User""" """
A mix between the database User and the Active Directory User
"""
type User { type User {
id: ID id: ID
wifiDevices: [WifiDevice!] wifiDevices: [WifiDevice!]
@ -175,7 +191,7 @@ type WifiDevice {
lastSeen: String lastSeen: String
essid: String essid: String
ip: String ip: String
uptime: String uptime: Int
apName: String apName: String
status: Status status: Status
} }

View File

@ -1,41 +1,41 @@
{ {
"name": "App", "name": "App",
"icons": [ "icons": [
{ {
"src": "\/android-icon-36x36.png", "src": "/android-icon-36x36.png",
"sizes": "36x36", "sizes": "36x36",
"type": "image\/png", "type": "image/png",
"density": "0.75" "density": "0.75"
}, },
{ {
"src": "\/android-icon-48x48.png", "src": "/android-icon-48x48.png",
"sizes": "48x48", "sizes": "48x48",
"type": "image\/png", "type": "image/png",
"density": "1.0" "density": "1.0"
}, },
{ {
"src": "\/android-icon-72x72.png", "src": "/android-icon-72x72.png",
"sizes": "72x72", "sizes": "72x72",
"type": "image\/png", "type": "image/png",
"density": "1.5" "density": "1.5"
}, },
{ {
"src": "\/android-icon-96x96.png", "src": "/android-icon-96x96.png",
"sizes": "96x96", "sizes": "96x96",
"type": "image\/png", "type": "image/png",
"density": "2.0" "density": "2.0"
}, },
{ {
"src": "\/android-icon-144x144.png", "src": "/android-icon-144x144.png",
"sizes": "144x144", "sizes": "144x144",
"type": "image\/png", "type": "image/png",
"density": "3.0" "density": "3.0"
}, },
{ {
"src": "\/android-icon-192x192.png", "src": "/android-icon-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image\/png", "type": "image/png",
"density": "4.0" "density": "4.0"
} }
] ]
} }

View File

@ -102,7 +102,8 @@ export default {
</script> </script>
<style scoped> <style scoped>
td { td,
td * {
white-space: nowrap; white-space: nowrap;
} }
</style> </style>

View File

@ -37,4 +37,9 @@ export default {
} }
</script> </script>
<style></style> <style scoped>
td,
td * {
white-space: nowrap;
}
</style>

View File

@ -10,6 +10,8 @@
:items="items" :items="items"
:headers="headers" :headers="headers"
:search="search" :search="search"
sort-by="status"
sort-desc
calculate-widths calculate-widths
> >
<template #[`item.status`]="{ item }"> <template #[`item.status`]="{ item }">
@ -108,7 +110,8 @@ export default {
</script> </script>
<style scoped> <style scoped>
td { td,
td * {
white-space: nowrap; white-space: nowrap;
} }
</style> </style>

View File

@ -30,7 +30,7 @@
</template> </template>
<template #[`item.uptime`]="{ item: { uptime } }"> <template #[`item.uptime`]="{ item: { uptime } }">
<small> {{ uptime | durationFromSeconds }}</small> <small class="text-no-wrap"> {{ uptime | durationFromSeconds }}</small>
</template> </template>
<template #[`item.signalStrength`]="{ item: { signalStrength, status } }"> <template #[`item.signalStrength`]="{ item: { signalStrength, status } }">
@ -131,7 +131,8 @@ export default {
</script> </script>
<style scoped> <style scoped>
td { td,
td * {
white-space: nowrap; white-space: nowrap;
} }
</style> </style>

View File

@ -55,4 +55,9 @@ export default {
} }
</script> </script>
<style></style> <style scoped>
td,
td * {
white-space: nowrap;
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<span> <span class="text-no-wrap">
{{ amount }} <small>{{ unit }}</small> {{ amount }} <small class="font-weight-medium">{{ unit }}</small>
</span> </span>
</template> </template>
@ -10,8 +10,8 @@ import { destructedBytes } from '../../plugins/format-bytes'
export default { export default {
props: { props: {
value: { value: {
type: Number, type: String,
default: 0 default: '0'
} }
}, },
computed: { computed: {

View File

@ -1,16 +1,19 @@
import Vue from 'vue' import Vue from 'vue'
export function destructedBytes(bytes, kib, maxUnit) { export function destructedBytes(bytes, kib, maxUnit) {
if (bytes == 0)
return {
amount: '0',
unit: 'Bytes'
}
if (isNaN(parseFloat(bytes)) && !isFinite(bytes))
return { amount: '', unit: 'Err' }
const decimals = 1 const decimals = 1
kib = kib || false kib = kib || false
if (bytes < 0) bytes = bytes * -1
if (bytes === 0) return '0 Bytes'
if (isNaN(parseFloat(bytes)) && !isFinite(bytes)) return ''
const k = kib ? 1024 : 1000 const k = kib ? 1024 : 1000
const sizes = kib const sizes = kib

View File

@ -144,7 +144,7 @@
<template #[`item.clients`]="{ item }"> <template #[`item.clients`]="{ item }">
<div class="align-content-end"> <div class="align-content-end">
<v-btn <v-btn
:disabled="item.uptime === '-1'" :disabled="item.uptime == -1"
block block
small small
rounded rounded
@ -183,6 +183,9 @@
<template #[`item.notes`]="{ item }"> <template #[`item.notes`]="{ item }">
<small>{{ item.notes }}</small> <small>{{ item.notes }}</small>
</template> </template>
<template #[`item.usage`]="{ item }">
<small>{{ item.usage | bytes }}</small>
</template>
</v-data-table> </v-data-table>
</v-container> </v-container>
</template> </template>
@ -206,6 +209,7 @@ export default {
'local', 'local',
'clients', 'clients',
'uptime', 'uptime',
'usage',
'notes' 'notes'
], ],
allHeaders: [ allHeaders: [
@ -214,7 +218,6 @@ export default {
{ text: 'IP', value: 'ip', width: 80 }, { text: 'IP', value: 'ip', width: 80 },
{ text: 'MAC', value: 'mac' }, { text: 'MAC', value: 'mac' },
{ text: 'Campus', value: 'campus', group: true }, { text: 'Campus', value: 'campus', group: true },
{ text: 'Localização', value: 'local' }, { text: 'Localização', value: 'local' },
{ {
text: 'Clientes', text: 'Clientes',
@ -228,6 +231,7 @@ export default {
text: 'Controladora', text: 'Controladora',
value: 'controller' value: 'controller'
}, },
{ text: 'Uso', value: 'usage' },
{ text: 'Observações', value: 'notes' }, { text: 'Observações', value: 'notes' },
{ text: 'Última atualização', value: 'updatedAt', width: 160 } { text: 'Última atualização', value: 'updatedAt', width: 160 }
] ]
@ -306,6 +310,7 @@ export default {
model model
ip ip
clients clients
usage
createdAt createdAt
updatedAt updatedAt
@ -332,6 +337,7 @@ export default {
model model
ip ip
clients clients
usage
createdAt createdAt
updatedAt updatedAt