feat: 添加遮罩筛选
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
d1c5b818d9
commit
d82872ad18
@ -141,9 +141,6 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) {
|
|||||||
<h2 className="mt-2 text-2xl font-semibold text-gray-900 dark:text-white">
|
<h2 className="mt-2 text-2xl font-semibold text-gray-900 dark:text-white">
|
||||||
{periodLabel}
|
{periodLabel}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
切换日期时会自动取消上一条请求,图表与排行保持同步刷新。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||||
@ -296,6 +293,10 @@ export default function ScreenTimeTab({ hostname }: ScreenTimeTabProps) {
|
|||||||
<span>{app.percentage}%</span>
|
<span>{app.percentage}%</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-2 truncate text-xs text-gray-500 dark:text-gray-400" title={app.processPath || '路径未知'}>
|
||||||
|
路径: {app.processPath || '路径未知'}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 h-2 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700">
|
<div className="mt-3 h-2 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700">
|
||||||
<div
|
<div
|
||||||
className="h-full rounded-full bg-blue-600 dark:bg-blue-500"
|
className="h-full rounded-full bg-blue-600 dark:bg-blue-500"
|
||||||
|
|||||||
@ -28,6 +28,7 @@ interface TitleUsage {
|
|||||||
interface MutableAppUsage {
|
interface MutableAppUsage {
|
||||||
processId: string
|
processId: string
|
||||||
rawProcessName: string
|
rawProcessName: string
|
||||||
|
rawProcessPath: string
|
||||||
durationMs: number
|
durationMs: number
|
||||||
titleDurations: Map<string, number>
|
titleDurations: Map<string, number>
|
||||||
}
|
}
|
||||||
@ -111,12 +112,14 @@ const getLocalDateUtcMs = (parts: DateParts, tzOffsetMinutes: number) => {
|
|||||||
|
|
||||||
const normalizeProcess = (processPath: string, title: string) => {
|
const normalizeProcess = (processPath: string, title: string) => {
|
||||||
const rawProcessName = win32.basename(processPath || '').trim()
|
const rawProcessName = win32.basename(processPath || '').trim()
|
||||||
|
const rawProcessPath = processPath.trim()
|
||||||
const normalizedTitle = cleanTitle(title)
|
const normalizedTitle = cleanTitle(title)
|
||||||
const fallbackId = normalizedTitle ? `title:${normalizedTitle.toLowerCase()}` : 'unknown'
|
const fallbackId = normalizedTitle ? `title:${normalizedTitle.toLowerCase()}` : 'unknown'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
processId: rawProcessName ? rawProcessName.toLowerCase() : fallbackId,
|
processId: rawProcessName ? rawProcessName.toLowerCase() : fallbackId,
|
||||||
rawProcessName
|
rawProcessName,
|
||||||
|
rawProcessPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +231,7 @@ const addDurationToUsageMap = (
|
|||||||
usageMap: Map<string, MutableAppUsage>,
|
usageMap: Map<string, MutableAppUsage>,
|
||||||
processId: string,
|
processId: string,
|
||||||
rawProcessName: string,
|
rawProcessName: string,
|
||||||
|
rawProcessPath: string,
|
||||||
title: string,
|
title: string,
|
||||||
durationMs: number
|
durationMs: number
|
||||||
) => {
|
) => {
|
||||||
@ -240,12 +244,16 @@ const addDurationToUsageMap = (
|
|||||||
if (!existing.rawProcessName && rawProcessName) {
|
if (!existing.rawProcessName && rawProcessName) {
|
||||||
existing.rawProcessName = rawProcessName
|
existing.rawProcessName = rawProcessName
|
||||||
}
|
}
|
||||||
|
if (!existing.rawProcessPath && rawProcessPath) {
|
||||||
|
existing.rawProcessPath = rawProcessPath
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usageMap.set(processId, {
|
usageMap.set(processId, {
|
||||||
processId,
|
processId,
|
||||||
rawProcessName,
|
rawProcessName,
|
||||||
|
rawProcessPath,
|
||||||
durationMs,
|
durationMs,
|
||||||
titleDurations: new Map([[title, durationMs]])
|
titleDurations: new Map([[title, durationMs]])
|
||||||
})
|
})
|
||||||
@ -266,6 +274,7 @@ const buildAppList = (usageMap: Map<string, MutableAppUsage>) => {
|
|||||||
processId: app.processId,
|
processId: app.processId,
|
||||||
processName: resolveProcessDisplayName(app.rawProcessName, sortedTitles),
|
processName: resolveProcessDisplayName(app.rawProcessName, sortedTitles),
|
||||||
rawProcessName: app.rawProcessName,
|
rawProcessName: app.rawProcessName,
|
||||||
|
processPath: app.rawProcessPath,
|
||||||
durationSeconds: Math.round(app.durationMs / 1000),
|
durationSeconds: Math.round(app.durationMs / 1000),
|
||||||
percentage: totalDurationMs > 0 ? Number(((app.durationMs / totalDurationMs) * 100).toFixed(2)) : 0,
|
percentage: totalDurationMs > 0 ? Number(((app.durationMs / totalDurationMs) * 100).toFixed(2)) : 0,
|
||||||
titleCount: sortedTitles.length,
|
titleCount: sortedTitles.length,
|
||||||
@ -384,6 +393,7 @@ async function handleScreenTime(req: NextRequest) {
|
|||||||
aggregateUsageMap,
|
aggregateUsageMap,
|
||||||
process.processId,
|
process.processId,
|
||||||
process.rawProcessName,
|
process.rawProcessName,
|
||||||
|
process.rawProcessPath,
|
||||||
title,
|
title,
|
||||||
aggregateContributionMs
|
aggregateContributionMs
|
||||||
)
|
)
|
||||||
@ -393,6 +403,7 @@ async function handleScreenTime(req: NextRequest) {
|
|||||||
selectedDayUsageMap,
|
selectedDayUsageMap,
|
||||||
process.processId,
|
process.processId,
|
||||||
process.rawProcessName,
|
process.rawProcessName,
|
||||||
|
process.rawProcessPath,
|
||||||
title,
|
title,
|
||||||
selectedDayContributionMs
|
selectedDayContributionMs
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { prisma } from '@/lib/prisma'
|
|||||||
import { storeFile } from '@/lib/fileStorage'
|
import { storeFile } from '@/lib/fileStorage'
|
||||||
import { push } from '@/lib/push'
|
import { push } from '@/lib/push'
|
||||||
import { withCors } from '@/lib/middleware'
|
import { withCors } from '@/lib/middleware'
|
||||||
|
import { filterIgnoredWindows } from '@/lib/windowFilters'
|
||||||
import ffmpeg from 'fluent-ffmpeg'
|
import ffmpeg from 'fluent-ffmpeg'
|
||||||
import { writeFileSync, unlinkSync, mkdtempSync, readFileSync, rmSync } from 'fs'
|
import { writeFileSync, unlinkSync, mkdtempSync, readFileSync, rmSync } from 'fs'
|
||||||
import { tmpdir } from 'os'
|
import { tmpdir } from 'os'
|
||||||
@ -28,7 +29,8 @@ async function handleScreenshotUpload(req: NextRequest) {
|
|||||||
|
|
||||||
const formData = await req.formData()
|
const formData = await req.formData()
|
||||||
const files: File[] = []
|
const files: File[] = []
|
||||||
const windowsInfo: WindowInfo[] = JSON.parse(formData.get('windows_info') as string || '[]')
|
const rawWindowsInfo: WindowInfo[] = JSON.parse(formData.get('windows_info') as string || '[]')
|
||||||
|
const windowsInfo = filterIgnoredWindows(rawWindowsInfo)
|
||||||
|
|
||||||
// Extract files from formData
|
// Extract files from formData
|
||||||
for (const [key, value] of formData.entries()) {
|
for (const [key, value] of formData.entries()) {
|
||||||
|
|||||||
@ -33,6 +33,7 @@ export interface ScreenTimeAppUsage {
|
|||||||
processId: string;
|
processId: string;
|
||||||
processName: string;
|
processName: string;
|
||||||
rawProcessName: string;
|
rawProcessName: string;
|
||||||
|
processPath: string;
|
||||||
durationSeconds: number;
|
durationSeconds: number;
|
||||||
percentage: number;
|
percentage: number;
|
||||||
titleCount: number;
|
titleCount: number;
|
||||||
|
|||||||
66
envtests/cleanup-nvidia-overlay.ts
Normal file
66
envtests/cleanup-nvidia-overlay.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import { prisma } from '../lib/prisma'
|
||||||
|
import { IGNORED_WINDOW_KEYWORDS } from '../lib/windowFilters'
|
||||||
|
|
||||||
|
async function cleanupIgnoredWindows() {
|
||||||
|
console.log(`开始清理数据库中的忽略窗口记录: ${IGNORED_WINDOW_KEYWORDS.join(', ')}`)
|
||||||
|
|
||||||
|
const whereClause = {
|
||||||
|
OR: [
|
||||||
|
...IGNORED_WINDOW_KEYWORDS.flatMap(keyword => ([
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
contains: keyword,
|
||||||
|
mode: 'insensitive' as const
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: {
|
||||||
|
contains: keyword,
|
||||||
|
mode: 'insensitive' as const
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const [matchedCount, affectedHosts] = await Promise.all([
|
||||||
|
prisma.window.count({ where: whereClause }),
|
||||||
|
prisma.window.findMany({
|
||||||
|
where: whereClause,
|
||||||
|
select: {
|
||||||
|
record: {
|
||||||
|
select: {
|
||||||
|
hostname: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
distinct: ['recordId']
|
||||||
|
})
|
||||||
|
])
|
||||||
|
|
||||||
|
if (matchedCount === 0) {
|
||||||
|
console.log('没有找到需要清理的忽略窗口记录。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostnames = Array.from(new Set(affectedHosts.map(item => item.record.hostname))).sort()
|
||||||
|
console.log(`匹配到 ${matchedCount} 条窗口记录,涉及 ${hostnames.length} 台主机。`)
|
||||||
|
console.log(`主机列表: ${hostnames.join(', ')}`)
|
||||||
|
|
||||||
|
const deleteResult = await prisma.window.deleteMany({
|
||||||
|
where: whereClause
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`已删除 ${deleteResult.count} 条忽略窗口记录。`)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupIgnoredWindows()
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('清理忽略窗口失败:', error)
|
||||||
|
process.exitCode = 1
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
})
|
||||||
20
lib/windowFilters.ts
Normal file
20
lib/windowFilters.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const IGNORED_WINDOW_KEYWORDS = [
|
||||||
|
'NVIDIA Overlay',
|
||||||
|
'Twinkle Tray Panel'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const normalizedIgnoredWindowKeywords = IGNORED_WINDOW_KEYWORDS.map(keyword => keyword.toLowerCase())
|
||||||
|
|
||||||
|
interface WindowLike {
|
||||||
|
title?: string | null
|
||||||
|
path?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIgnoredWindow = ({ title, path }: WindowLike) => {
|
||||||
|
const normalizedValue = `${title ?? ''}\n${path ?? ''}`.toLowerCase()
|
||||||
|
return normalizedIgnoredWindowKeywords.some(keyword => normalizedValue.includes(keyword))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const filterIgnoredWindows = <T extends WindowLike>(windows: T[]) => {
|
||||||
|
return windows.filter(window => !isIgnoredWindow(window))
|
||||||
|
}
|
||||||
@ -10,7 +10,9 @@
|
|||||||
"db:generate": "prisma generate",
|
"db:generate": "prisma generate",
|
||||||
"db:migrate": "prisma migrate dev",
|
"db:migrate": "prisma migrate dev",
|
||||||
"db:reset": "prisma migrate reset",
|
"db:reset": "prisma migrate reset",
|
||||||
"db:studio": "prisma studio"
|
"db:studio": "prisma studio",
|
||||||
|
"db:cleanup:ignored-windows": "bun envtests/cleanup-nvidia-overlay.ts",
|
||||||
|
"db:cleanup:nvidia-overlay": "bun envtests/cleanup-nvidia-overlay.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.10.1",
|
"@prisma/client": "^6.10.1",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user