This commit is contained in:
Luís Miguel 2026-03-27 07:14:54 -03:00 committed by GitHub
commit f820e0d3b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 151 additions and 1 deletions

View File

@ -113,6 +113,10 @@ export default class SystemController {
return await this.systemService.subscribeToReleaseNotes(reqData.email);
}
async getDiskStatus({}: HttpContext) {
return await this.systemService.getDiskStatus();
}
async getDebugInfo({}: HttpContext) {
const debugInfo = await this.systemService.getDebugInfo()
return { debugInfo }

View File

@ -521,6 +521,59 @@ export class SystemService {
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i]
}
async getDiskStatus(): Promise<{
level: 'none' | 'warning' | 'critical'
threshold: number
highestUsage: number
diskName: string
}> {
try {
const diskInfoRawString = await getFile(
path.join(process.cwd(), SystemService.diskInfoFile),
'string'
)
const diskInfo = (
diskInfoRawString
? JSON.parse(diskInfoRawString.toString())
: { diskLayout: { blockdevices: [] }, fsSize: [] }
) as NomadDiskInfoRaw
const disks = this.calculateDiskUsage(diskInfo)
const warningStr = await KVStore.getValue('disk.warningThreshold')
const criticalStr = await KVStore.getValue('disk.criticalThreshold')
const warningThreshold = warningStr ? Number(warningStr) : 85
const criticalThreshold = criticalStr ? Number(criticalStr) : 95
let highestUsage = 0
let diskName = ''
for (const disk of disks) {
if (disk.percentUsed > highestUsage) {
highestUsage = disk.percentUsed
diskName = disk.name
}
}
let level: 'none' | 'warning' | 'critical' = 'none'
let threshold = 0
if (highestUsage >= criticalThreshold) {
level = 'critical'
threshold = criticalThreshold
} else if (highestUsage >= warningThreshold) {
level = 'warning'
threshold = warningThreshold
}
return { level, threshold, highestUsage, diskName }
} catch (error) {
logger.error('Error getting disk status:', error)
return { level: 'none', threshold: 0, highestUsage: 0, diskName: '' }
}
}
async updateSetting(key: KVStoreKey, value: any): Promise<void> {
if ((value === '' || value === undefined || value === null) && KV_STORE_SCHEMA[key] === 'string') {
await KVStore.clearValue(key)

View File

@ -1,3 +1,3 @@
import { KVStoreKey } from "../types/kv_store.js";
export const SETTINGS_KEYS: KVStoreKey[] = ['chat.suggestionsEnabled', 'chat.lastModel', 'ui.hasVisitedEasySetup', 'ui.theme', 'system.earlyAccess', 'ai.assistantCustomName'];
export const SETTINGS_KEYS: KVStoreKey[] = ['chat.suggestionsEnabled', 'chat.lastModel', 'ui.hasVisitedEasySetup', 'ui.theme', 'system.earlyAccess', 'ai.assistantCustomName', 'disk.warningThreshold', 'disk.criticalThreshold'];

View File

@ -0,0 +1,23 @@
import Alert from './Alert'
import { useDiskAlert } from '~/hooks/useDiskAlert'
export default function DiskAlertBanner() {
const { diskStatus, shouldShow, dismiss } = useDiskAlert()
if (!shouldShow || !diskStatus) return null
const isWarning = diskStatus.level === 'warning'
return (
<div className="px-4 pt-4">
<Alert
type={isWarning ? 'warning' : 'error'}
variant="bordered"
title={isWarning ? 'Disk Space Running Low' : 'Disk Space Critically Low'}
message={`Disk "${diskStatus.diskName}" is ${diskStatus.highestUsage.toFixed(1)}% full. Free up space to avoid issues with downloads and services.`}
dismissible={true}
onDismiss={dismiss}
/>
</div>
)
}

View File

@ -0,0 +1,53 @@
import { useQuery } from '@tanstack/react-query'
import { useState, useMemo } from 'react'
import api from '~/lib/api'
type DiskAlertLevel = 'none' | 'warning' | 'critical'
interface DiskStatusResponse {
level: DiskAlertLevel
threshold: number
highestUsage: number
diskName: string
}
const DISMISSED_KEY = 'nomad:disk-alert-dismissed-level'
export function useDiskAlert() {
const [dismissedLevel, setDismissedLevel] = useState<DiskAlertLevel | null>(() => {
try {
return localStorage.getItem(DISMISSED_KEY) as DiskAlertLevel | null
} catch {
return null
}
})
const { data: diskStatus } = useQuery<DiskStatusResponse | undefined>({
queryKey: ['disk-status'],
queryFn: async () => await api.getDiskStatus(),
refetchInterval: 45000,
})
const shouldShow = useMemo(() => {
if (!diskStatus || diskStatus.level === 'none') return false
if (!dismissedLevel) return true
// Exibe novamente se o nível piorou
if (dismissedLevel === 'warning' && diskStatus.level === 'critical') return true
return false
}, [diskStatus, dismissedLevel])
const dismiss = () => {
if (diskStatus) {
setDismissedLevel(diskStatus.level)
try {
localStorage.setItem(DISMISSED_KEY, diskStatus.level)
} catch {}
}
}
return {
diskStatus,
shouldShow,
dismiss,
}
}

View File

@ -7,6 +7,7 @@ import { SERVICE_NAMES } from '../../constants/service_names'
import { Link } from '@inertiajs/react'
import { IconArrowLeft } from '@tabler/icons-react'
import classNames from 'classnames'
import DiskAlertBanner from '~/components/DiskAlertBanner'
export default function AppLayout({ children }: { children: React.ReactNode }) {
const [isChatOpen, setIsChatOpen] = useState(false)
@ -33,6 +34,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
"text-desert-green font-semibold h-[1.5px] bg-desert-green border-none",
window.location.pathname !== '/home' ? "mt-12 md:mt-0" : "mt-0"
)} />
<DiskAlertBanner />
<div className="flex-1 w-full bg-desert">{children}</div>
<Footer />

View File

@ -223,6 +223,18 @@ class API {
})()
}
async getDiskStatus() {
return catchInternal(async () => {
const response = await this.client.get<{
level: 'none' | 'warning' | 'critical'
threshold: number
highestUsage: number
diskName: string
}>('/system/disk-status')
return response.data
})()
}
async getDebugInfo() {
return catchInternal(async () => {
const response = await this.client.get<{ debugInfo: string }>('/system/debug-info')

View File

@ -137,6 +137,7 @@ router
router
.group(() => {
router.get('/disk-status', [SystemController, 'getDiskStatus'])
router.get('/debug-info', [SystemController, 'getDebugInfo'])
router.get('/info', [SystemController, 'getSystemInfo'])
router.get('/internet-status', [SystemController, 'getInternetStatus'])

View File

@ -10,6 +10,8 @@ export const KV_STORE_SCHEMA = {
'ui.theme': 'string',
'ai.assistantCustomName': 'string',
'gpu.type': 'string',
'disk.warningThreshold': 'string',
'disk.criticalThreshold': 'string',
} as const
type KVTagToType<T extends string> = T extends 'boolean' ? boolean : string