mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
Merge b36fe09932 into 44ecf41ca6
This commit is contained in:
commit
06e6185641
|
|
@ -1,3 +1,3 @@
|
||||||
import { KVStoreKey } from "../types/kv_store.js";
|
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', 'ui.language'];
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/// <reference path="../../adonisrc.ts" />
|
/// <reference path="../../adonisrc.ts" />
|
||||||
/// <reference path="../../config/inertia.ts" />
|
/// <reference path="../../config/inertia.ts" />
|
||||||
|
|
||||||
|
import '~/lib/i18n'
|
||||||
import '../css/app.css'
|
import '../css/app.css'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import { createInertiaApp } from '@inertiajs/react'
|
import { createInertiaApp } from '@inertiajs/react'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { usePage } from '@inertiajs/react'
|
import { usePage } from '@inertiajs/react'
|
||||||
import { UsePageProps } from '../../types/system'
|
import { UsePageProps } from '../../types/system'
|
||||||
import ThemeToggle from '~/components/ThemeToggle'
|
import ThemeToggle from '~/components/ThemeToggle'
|
||||||
|
|
@ -6,6 +7,7 @@ import { IconBug } from '@tabler/icons-react'
|
||||||
import DebugInfoModal from './DebugInfoModal'
|
import DebugInfoModal from './DebugInfoModal'
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
|
const { t } = useTranslation('layout')
|
||||||
const { appVersion } = usePage().props as unknown as UsePageProps
|
const { appVersion } = usePage().props as unknown as UsePageProps
|
||||||
const [debugModalOpen, setDebugModalOpen] = useState(false)
|
const [debugModalOpen, setDebugModalOpen] = useState(false)
|
||||||
|
|
||||||
|
|
@ -13,7 +15,7 @@ export default function Footer() {
|
||||||
<footer>
|
<footer>
|
||||||
<div className="flex items-center justify-center gap-3 border-t border-border-subtle py-4">
|
<div className="flex items-center justify-center gap-3 border-t border-border-subtle py-4">
|
||||||
<p className="text-sm/6 text-text-secondary">
|
<p className="text-sm/6 text-text-secondary">
|
||||||
Project N.O.M.A.D. Command Center v{appVersion}
|
{t('footer.version', { version: appVersion })}
|
||||||
</p>
|
</p>
|
||||||
<span className="text-gray-300">|</span>
|
<span className="text-gray-300">|</span>
|
||||||
<button
|
<button
|
||||||
|
|
@ -21,7 +23,7 @@ export default function Footer() {
|
||||||
className="text-sm/6 text-gray-500 hover:text-desert-green flex items-center gap-1 cursor-pointer"
|
className="text-sm/6 text-gray-500 hover:text-desert-green flex items-center gap-1 cursor-pointer"
|
||||||
>
|
>
|
||||||
<IconBug className="size-3.5" />
|
<IconBug className="size-3.5" />
|
||||||
Debug Info
|
{t('footer.debugInfo')}
|
||||||
</button>
|
</button>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import Footer from '~/components/Footer'
|
import Footer from '~/components/Footer'
|
||||||
import ChatButton from '~/components/chat/ChatButton'
|
import ChatButton from '~/components/chat/ChatButton'
|
||||||
import ChatModal from '~/components/chat/ChatModal'
|
import ChatModal from '~/components/chat/ChatModal'
|
||||||
|
|
@ -9,6 +10,7 @@ import { IconArrowLeft } from '@tabler/icons-react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const { t } = useTranslation('layout')
|
||||||
const [isChatOpen, setIsChatOpen] = useState(false)
|
const [isChatOpen, setIsChatOpen] = useState(false)
|
||||||
const aiAssistantInstalled = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
const aiAssistantInstalled = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
||||||
|
|
||||||
|
|
@ -18,7 +20,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||||
window.location.pathname !== '/home' && (
|
window.location.pathname !== '/home' && (
|
||||||
<Link href="/home" className="absolute top-60 md:top-48 left-4 flex items-center">
|
<Link href="/home" className="absolute top-60 md:top-48 left-4 flex items-center">
|
||||||
<IconArrowLeft className="mr-2" size={24} />
|
<IconArrowLeft className="mr-2" size={24} />
|
||||||
<p className="text-lg text-text-secondary">Back to Home</p>
|
<p className="text-lg text-text-secondary">{t('nav.backToHome')}</p>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
|
|
@ -26,7 +28,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||||
onClick={() => (window.location.href = '/home')}
|
onClick={() => (window.location.href = '/home')}
|
||||||
>
|
>
|
||||||
<img src="/project_nomad_logo.png" alt="Project Nomad Logo" className="h-40 w-40" />
|
<img src="/project_nomad_logo.png" alt="Project Nomad Logo" className="h-40 w-40" />
|
||||||
<h1 className="text-5xl font-bold text-desert-green">Command Center</h1>
|
<h1 className="text-5xl font-bold text-desert-green">{t('header.commandCenter')}</h1>
|
||||||
</div>
|
</div>
|
||||||
<hr className={
|
<hr className={
|
||||||
classNames(
|
classNames(
|
||||||
|
|
|
||||||
|
|
@ -12,43 +12,45 @@ import {
|
||||||
IconZoom
|
IconZoom
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { usePage } from '@inertiajs/react'
|
import { usePage } from '@inertiajs/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import StyledSidebar from '~/components/StyledSidebar'
|
import StyledSidebar from '~/components/StyledSidebar'
|
||||||
import { getServiceLink } from '~/lib/navigation'
|
import { getServiceLink } from '~/lib/navigation'
|
||||||
import useServiceInstalledStatus from '~/hooks/useServiceInstalledStatus'
|
import useServiceInstalledStatus from '~/hooks/useServiceInstalledStatus'
|
||||||
import { SERVICE_NAMES } from '../../constants/service_names'
|
import { SERVICE_NAMES } from '../../constants/service_names'
|
||||||
|
|
||||||
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const { t } = useTranslation('layout')
|
||||||
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
||||||
const aiAssistantInstallStatus = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
const aiAssistantInstallStatus = useServiceInstalledStatus(SERVICE_NAMES.OLLAMA)
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
...(aiAssistantInstallStatus.isInstalled ? [{ name: aiAssistantName, href: '/settings/models', icon: IconWand, current: false }] : []),
|
...(aiAssistantInstallStatus.isInstalled ? [{ name: aiAssistantName, href: '/settings/models', icon: IconWand, current: false }] : []),
|
||||||
{ name: 'Apps', href: '/settings/apps', icon: IconTerminal2, current: false },
|
{ name: t('sidebar.apps'), href: '/settings/apps', icon: IconTerminal2, current: false },
|
||||||
{ name: 'Benchmark', href: '/settings/benchmark', icon: IconChartBar, current: false },
|
{ name: t('sidebar.benchmark'), href: '/settings/benchmark', icon: IconChartBar, current: false },
|
||||||
{ name: 'Content Explorer', href: '/settings/zim/remote-explorer', icon: IconZoom, current: false },
|
{ name: t('sidebar.contentExplorer'), href: '/settings/zim/remote-explorer', icon: IconZoom, current: false },
|
||||||
{ name: 'Content Manager', href: '/settings/zim', icon: IconFolder, current: false },
|
{ name: t('sidebar.contentManager'), href: '/settings/zim', icon: IconFolder, current: false },
|
||||||
{ name: 'Maps Manager', href: '/settings/maps', icon: IconMapRoute, current: false },
|
{ name: t('sidebar.mapsManager'), href: '/settings/maps', icon: IconMapRoute, current: false },
|
||||||
{
|
{
|
||||||
name: 'Service Logs & Metrics',
|
name: t('sidebar.serviceLogs'),
|
||||||
href: getServiceLink('9999'),
|
href: getServiceLink('9999'),
|
||||||
icon: IconDashboard,
|
icon: IconDashboard,
|
||||||
current: false,
|
current: false,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Check for Updates',
|
name: t('sidebar.checkUpdates'),
|
||||||
href: '/settings/update',
|
href: '/settings/update',
|
||||||
icon: IconArrowBigUpLines,
|
icon: IconArrowBigUpLines,
|
||||||
current: false,
|
current: false,
|
||||||
},
|
},
|
||||||
{ name: 'System', href: '/settings/system', icon: IconSettings, current: false },
|
{ name: t('sidebar.system'), href: '/settings/system', icon: IconSettings, current: false },
|
||||||
{ name: 'Support the Project', href: '/settings/support', icon: IconHeart, current: false },
|
{ name: t('sidebar.support'), href: '/settings/support', icon: IconHeart, current: false },
|
||||||
{ name: 'Legal Notices', href: '/settings/legal', icon: IconGavel, current: false },
|
{ name: t('sidebar.legal'), href: '/settings/legal', icon: IconGavel, current: false },
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-row bg-surface-secondary/90">
|
<div className="min-h-screen flex flex-row bg-surface-secondary/90">
|
||||||
<StyledSidebar title="Settings" items={navigation} />
|
<StyledSidebar title={t('sidebar.settings')} items={navigation} />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
46
admin/inertia/lib/i18n.ts
Normal file
46
admin/inertia/lib/i18n.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import i18n from 'i18next'
|
||||||
|
import { initReactI18next } from 'react-i18next'
|
||||||
|
|
||||||
|
// Import all translation files
|
||||||
|
import enCommon from '~/locales/en/common.json'
|
||||||
|
import enHome from '~/locales/en/home.json'
|
||||||
|
import enSettings from '~/locales/en/settings.json'
|
||||||
|
import enLayout from '~/locales/en/layout.json'
|
||||||
|
|
||||||
|
import ptBRCommon from '~/locales/pt-BR/common.json'
|
||||||
|
import ptBRHome from '~/locales/pt-BR/home.json'
|
||||||
|
import ptBRSettings from '~/locales/pt-BR/settings.json'
|
||||||
|
import ptBRLayout from '~/locales/pt-BR/layout.json'
|
||||||
|
|
||||||
|
const savedLanguage = (() => {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem('nomad:language') || 'en'
|
||||||
|
} catch {
|
||||||
|
return 'en'
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
i18n.use(initReactI18next).init({
|
||||||
|
resources: {
|
||||||
|
en: {
|
||||||
|
common: enCommon,
|
||||||
|
home: enHome,
|
||||||
|
settings: enSettings,
|
||||||
|
layout: enLayout,
|
||||||
|
},
|
||||||
|
'pt-BR': {
|
||||||
|
common: ptBRCommon,
|
||||||
|
home: ptBRHome,
|
||||||
|
settings: ptBRSettings,
|
||||||
|
layout: ptBRLayout,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lng: savedLanguage,
|
||||||
|
fallbackLng: 'en',
|
||||||
|
defaultNS: 'common',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n
|
||||||
29
admin/inertia/locales/en/common.json
Normal file
29
admin/inertia/locales/en/common.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"buttons": {
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"goToSettings": "Go to Settings",
|
||||||
|
"reinstall": "Reinstall"
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"updateAvailable": "An update is available for Project N.O.M.A.D.!",
|
||||||
|
"diskWarningTitle": "Disk Space Running Low",
|
||||||
|
"diskCriticalTitle": "Disk Space Critically Low",
|
||||||
|
"diskMessage": "Disk \"{{diskName}}\" is {{usage}}% full. Free up space to avoid issues with downloads and services.",
|
||||||
|
"highMemoryTitle": "Very High Memory Usage Detected",
|
||||||
|
"highMemoryMessage": "System memory usage exceeds 90%. Performance degradation may occur.",
|
||||||
|
"gpuNotAccessibleTitle": "GPU Not Accessible to AI Assistant",
|
||||||
|
"gpuNotAccessibleMessage": "Your system has an NVIDIA GPU, but the AI Assistant can't access it. AI is running on CPU only, which is significantly slower.",
|
||||||
|
"fixReinstallAI": "Fix: Reinstall AI Assistant",
|
||||||
|
"reinstallAIConfirmTitle": "Reinstall AI Assistant?",
|
||||||
|
"reinstallAIConfirmMessage": "This will recreate the AI Assistant container with GPU support enabled. Your downloaded models will be preserved. The service will be briefly unavailable during reinstall.",
|
||||||
|
"reinstallSuccess": "AI Assistant is being reinstalled with GPU support. This page will reload shortly.",
|
||||||
|
"reinstallFailed": "Failed to reinstall: {{error}}"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"label": "Language",
|
||||||
|
"en": "English",
|
||||||
|
"pt-BR": "Português (Brasil)"
|
||||||
|
}
|
||||||
|
}
|
||||||
24
admin/inertia/locales/en/home.json
Normal file
24
admin/inertia/locales/en/home.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"title": "Command Center",
|
||||||
|
"easySetup": {
|
||||||
|
"label": "Easy Setup",
|
||||||
|
"description": "Not sure where to start? Use the setup wizard to quickly configure your N.O.M.A.D.!",
|
||||||
|
"badge": "Start here!"
|
||||||
|
},
|
||||||
|
"installApps": {
|
||||||
|
"label": "Install Apps",
|
||||||
|
"description": "Not seeing your favorite app? Install it here!"
|
||||||
|
},
|
||||||
|
"docs": {
|
||||||
|
"label": "Docs",
|
||||||
|
"description": "Read Project N.O.M.A.D. manuals and guides"
|
||||||
|
},
|
||||||
|
"settingsCard": {
|
||||||
|
"label": "Settings",
|
||||||
|
"description": "Configure your N.O.M.A.D. settings"
|
||||||
|
},
|
||||||
|
"maps": {
|
||||||
|
"label": "Maps",
|
||||||
|
"description": "View offline maps"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
admin/inertia/locales/en/layout.json
Normal file
25
admin/inertia/locales/en/layout.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"commandCenter": "Command Center"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"backToHome": "Back to Home"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"version": "Project N.O.M.A.D. Command Center v{{version}}",
|
||||||
|
"debugInfo": "Debug Info"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"settings": "Settings",
|
||||||
|
"apps": "Apps",
|
||||||
|
"benchmark": "Benchmark",
|
||||||
|
"contentExplorer": "Content Explorer",
|
||||||
|
"contentManager": "Content Manager",
|
||||||
|
"mapsManager": "Maps Manager",
|
||||||
|
"serviceLogs": "Service Logs & Metrics",
|
||||||
|
"checkUpdates": "Check for Updates",
|
||||||
|
"system": "System",
|
||||||
|
"support": "Support the Project",
|
||||||
|
"legal": "Legal Notices"
|
||||||
|
}
|
||||||
|
}
|
||||||
57
admin/inertia/locales/en/settings.json
Normal file
57
admin/inertia/locales/en/settings.json
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"system": {
|
||||||
|
"title": "System Information",
|
||||||
|
"subtitle": "Real-time monitoring and diagnostics",
|
||||||
|
"lastUpdated": "Last updated: {{time}}",
|
||||||
|
"refreshing": "Refreshing data every 30 seconds"
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"resourceUsage": "Resource Usage",
|
||||||
|
"systemDetails": "System Details",
|
||||||
|
"memoryAllocation": "Memory Allocation",
|
||||||
|
"storageDevices": "Storage Devices",
|
||||||
|
"systemStatus": "System Status",
|
||||||
|
"preferences": "Preferences"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"cpuUsage": "CPU Usage",
|
||||||
|
"memoryUsage": "Memory Usage",
|
||||||
|
"swapUsage": "Swap Usage",
|
||||||
|
"cores": "{{count}} cores",
|
||||||
|
"utilized": "{{percent}}% Utilized",
|
||||||
|
"totalRam": "Total RAM",
|
||||||
|
"usedRam": "Used RAM",
|
||||||
|
"availableRam": "Available RAM",
|
||||||
|
"systemUptime": "System Uptime",
|
||||||
|
"cpuCores": "CPU Cores",
|
||||||
|
"storageDevicesCount": "Storage Devices",
|
||||||
|
"noStorageDetected": "No storage devices detected"
|
||||||
|
},
|
||||||
|
"os": {
|
||||||
|
"distribution": "Distribution",
|
||||||
|
"kernelVersion": "Kernel Version",
|
||||||
|
"architecture": "Architecture",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"platform": "Platform"
|
||||||
|
},
|
||||||
|
"cpu": {
|
||||||
|
"manufacturer": "Manufacturer",
|
||||||
|
"brand": "Brand",
|
||||||
|
"cores": "Cores",
|
||||||
|
"physicalCores": "Physical Cores",
|
||||||
|
"virtualization": "Virtualization",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled"
|
||||||
|
},
|
||||||
|
"gpu": {
|
||||||
|
"title": "Graphics",
|
||||||
|
"model": "Model",
|
||||||
|
"vendor": "Vendor",
|
||||||
|
"vram": "VRAM"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"normal": "Normal",
|
||||||
|
"warningHigh": "Warning - Usage High",
|
||||||
|
"criticalFull": "Critical - Disk Almost Full"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
admin/inertia/locales/pt-BR/common.json
Normal file
29
admin/inertia/locales/pt-BR/common.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"buttons": {
|
||||||
|
"save": "Salvar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"goToSettings": "Ir para Configurações",
|
||||||
|
"reinstall": "Reinstalar"
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"updateAvailable": "Uma atualização está disponível para o Project N.O.M.A.D.!",
|
||||||
|
"diskWarningTitle": "Espaço em disco ficando baixo",
|
||||||
|
"diskCriticalTitle": "Espaço em disco criticamente baixo",
|
||||||
|
"diskMessage": "O disco \"{{diskName}}\" está {{usage}}% cheio. Libere espaço para evitar problemas com downloads e serviços.",
|
||||||
|
"highMemoryTitle": "Uso de memória muito alto detectado",
|
||||||
|
"highMemoryMessage": "O uso de memória do sistema excede 90%. Pode haver degradação de desempenho.",
|
||||||
|
"gpuNotAccessibleTitle": "GPU não acessível pelo Assistente de IA",
|
||||||
|
"gpuNotAccessibleMessage": "Seu sistema possui uma GPU NVIDIA, mas o Assistente de IA não consegue acessá-la. A IA está rodando apenas na CPU, o que é significativamente mais lento.",
|
||||||
|
"fixReinstallAI": "Corrigir: Reinstalar Assistente de IA",
|
||||||
|
"reinstallAIConfirmTitle": "Reinstalar Assistente de IA?",
|
||||||
|
"reinstallAIConfirmMessage": "Isso recriará o contêiner do Assistente de IA com suporte a GPU habilitado. Seus modelos baixados serão preservados. O serviço ficará brevemente indisponível durante a reinstalação.",
|
||||||
|
"reinstallSuccess": "O Assistente de IA está sendo reinstalado com suporte a GPU. Esta página será recarregada em breve.",
|
||||||
|
"reinstallFailed": "Falha ao reinstalar: {{error}}"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"label": "Idioma",
|
||||||
|
"en": "English",
|
||||||
|
"pt-BR": "Português (Brasil)"
|
||||||
|
}
|
||||||
|
}
|
||||||
24
admin/inertia/locales/pt-BR/home.json
Normal file
24
admin/inertia/locales/pt-BR/home.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"title": "Central de Comando",
|
||||||
|
"easySetup": {
|
||||||
|
"label": "Configuração Rápida",
|
||||||
|
"description": "Não sabe por onde começar? Use o assistente de configuração para configurar seu N.O.M.A.D. rapidamente!",
|
||||||
|
"badge": "Comece aqui!"
|
||||||
|
},
|
||||||
|
"installApps": {
|
||||||
|
"label": "Instalar Apps",
|
||||||
|
"description": "Não encontra seu app favorito? Instale aqui!"
|
||||||
|
},
|
||||||
|
"docs": {
|
||||||
|
"label": "Documentação",
|
||||||
|
"description": "Leia os manuais e guias do Project N.O.M.A.D."
|
||||||
|
},
|
||||||
|
"settingsCard": {
|
||||||
|
"label": "Configurações",
|
||||||
|
"description": "Configure as opções do seu N.O.M.A.D."
|
||||||
|
},
|
||||||
|
"maps": {
|
||||||
|
"label": "Mapas",
|
||||||
|
"description": "Visualizar mapas offline"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
admin/inertia/locales/pt-BR/layout.json
Normal file
25
admin/inertia/locales/pt-BR/layout.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"commandCenter": "Central de Comando"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"backToHome": "Voltar ao Início"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"version": "Project N.O.M.A.D. Central de Comando v{{version}}",
|
||||||
|
"debugInfo": "Informações de Depuração"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"settings": "Configurações",
|
||||||
|
"apps": "Aplicativos",
|
||||||
|
"benchmark": "Benchmark",
|
||||||
|
"contentExplorer": "Explorador de Conteúdo",
|
||||||
|
"contentManager": "Gerenciador de Conteúdo",
|
||||||
|
"mapsManager": "Gerenciador de Mapas",
|
||||||
|
"serviceLogs": "Logs e Métricas de Serviços",
|
||||||
|
"checkUpdates": "Verificar Atualizações",
|
||||||
|
"system": "Sistema",
|
||||||
|
"support": "Apoiar o Projeto",
|
||||||
|
"legal": "Avisos Legais"
|
||||||
|
}
|
||||||
|
}
|
||||||
57
admin/inertia/locales/pt-BR/settings.json
Normal file
57
admin/inertia/locales/pt-BR/settings.json
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"system": {
|
||||||
|
"title": "Informações do Sistema",
|
||||||
|
"subtitle": "Monitoramento e diagnósticos em tempo real",
|
||||||
|
"lastUpdated": "Última atualização: {{time}}",
|
||||||
|
"refreshing": "Atualizando dados a cada 30 segundos"
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"resourceUsage": "Uso de Recursos",
|
||||||
|
"systemDetails": "Detalhes do Sistema",
|
||||||
|
"memoryAllocation": "Alocação de Memória",
|
||||||
|
"storageDevices": "Dispositivos de Armazenamento",
|
||||||
|
"systemStatus": "Status do Sistema",
|
||||||
|
"preferences": "Preferências"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"cpuUsage": "Uso de CPU",
|
||||||
|
"memoryUsage": "Uso de Memória",
|
||||||
|
"swapUsage": "Uso de Swap",
|
||||||
|
"cores": "{{count}} núcleos",
|
||||||
|
"utilized": "{{percent}}% Utilizado",
|
||||||
|
"totalRam": "RAM Total",
|
||||||
|
"usedRam": "RAM Usada",
|
||||||
|
"availableRam": "RAM Disponível",
|
||||||
|
"systemUptime": "Tempo Ativo",
|
||||||
|
"cpuCores": "Núcleos de CPU",
|
||||||
|
"storageDevicesCount": "Dispositivos de Armazenamento",
|
||||||
|
"noStorageDetected": "Nenhum dispositivo de armazenamento detectado"
|
||||||
|
},
|
||||||
|
"os": {
|
||||||
|
"distribution": "Distribuição",
|
||||||
|
"kernelVersion": "Versão do Kernel",
|
||||||
|
"architecture": "Arquitetura",
|
||||||
|
"hostname": "Nome do Host",
|
||||||
|
"platform": "Plataforma"
|
||||||
|
},
|
||||||
|
"cpu": {
|
||||||
|
"manufacturer": "Fabricante",
|
||||||
|
"brand": "Modelo",
|
||||||
|
"cores": "Núcleos",
|
||||||
|
"physicalCores": "Núcleos Físicos",
|
||||||
|
"virtualization": "Virtualização",
|
||||||
|
"enabled": "Habilitado",
|
||||||
|
"disabled": "Desabilitado"
|
||||||
|
},
|
||||||
|
"gpu": {
|
||||||
|
"title": "Placa de Vídeo",
|
||||||
|
"model": "Modelo",
|
||||||
|
"vendor": "Fabricante",
|
||||||
|
"vram": "VRAM"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"normal": "Normal",
|
||||||
|
"warningHigh": "Alerta - Uso Elevado",
|
||||||
|
"criticalFull": "Crítico - Disco Quase Cheio"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
IconWifiOff,
|
IconWifiOff,
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { Head, usePage } from '@inertiajs/react'
|
import { Head, usePage } from '@inertiajs/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import AppLayout from '~/layouts/AppLayout'
|
import AppLayout from '~/layouts/AppLayout'
|
||||||
import { getServiceLink } from '~/lib/navigation'
|
import { getServiceLink } from '~/lib/navigation'
|
||||||
import { ServiceSlim } from '../../types/services'
|
import { ServiceSlim } from '../../types/services'
|
||||||
|
|
@ -16,63 +17,6 @@ import { useSystemSetting } from '~/hooks/useSystemSetting'
|
||||||
import Alert from '~/components/Alert'
|
import Alert from '~/components/Alert'
|
||||||
import { SERVICE_NAMES } from '../../constants/service_names'
|
import { SERVICE_NAMES } from '../../constants/service_names'
|
||||||
|
|
||||||
// Maps is a Core Capability (display_order: 4)
|
|
||||||
const MAPS_ITEM = {
|
|
||||||
label: 'Maps',
|
|
||||||
to: '/maps',
|
|
||||||
target: '',
|
|
||||||
description: 'View offline maps',
|
|
||||||
icon: <IconMapRoute size={48} />,
|
|
||||||
installed: true,
|
|
||||||
displayOrder: 4,
|
|
||||||
poweredBy: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
// System items shown after all apps
|
|
||||||
const SYSTEM_ITEMS = [
|
|
||||||
{
|
|
||||||
label: 'Easy Setup',
|
|
||||||
to: '/easy-setup',
|
|
||||||
target: '',
|
|
||||||
description:
|
|
||||||
'Not sure where to start? Use the setup wizard to quickly configure your N.O.M.A.D.!',
|
|
||||||
icon: <IconBolt size={48} />,
|
|
||||||
installed: true,
|
|
||||||
displayOrder: 50,
|
|
||||||
poweredBy: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Install Apps',
|
|
||||||
to: '/settings/apps',
|
|
||||||
target: '',
|
|
||||||
description: 'Not seeing your favorite app? Install it here!',
|
|
||||||
icon: <IconPlus size={48} />,
|
|
||||||
installed: true,
|
|
||||||
displayOrder: 51,
|
|
||||||
poweredBy: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Docs',
|
|
||||||
to: '/docs/home',
|
|
||||||
target: '',
|
|
||||||
description: 'Read Project N.O.M.A.D. manuals and guides',
|
|
||||||
icon: <IconHelp size={48} />,
|
|
||||||
installed: true,
|
|
||||||
displayOrder: 52,
|
|
||||||
poweredBy: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Settings',
|
|
||||||
to: '/settings/system',
|
|
||||||
target: '',
|
|
||||||
description: 'Configure your N.O.M.A.D. settings',
|
|
||||||
icon: <IconSettings size={48} />,
|
|
||||||
installed: true,
|
|
||||||
displayOrder: 53,
|
|
||||||
poweredBy: null,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
interface DashboardItem {
|
interface DashboardItem {
|
||||||
label: string
|
label: string
|
||||||
to: string
|
to: string
|
||||||
|
|
@ -89,6 +33,8 @@ export default function Home(props: {
|
||||||
services: ServiceSlim[]
|
services: ServiceSlim[]
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation('home')
|
||||||
|
const { t: tCommon } = useTranslation('common')
|
||||||
const items: DashboardItem[] = []
|
const items: DashboardItem[] = []
|
||||||
const updateInfo = useUpdateAvailable();
|
const updateInfo = useUpdateAvailable();
|
||||||
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
const { aiAssistantName } = usePage<{ aiAssistantName: string }>().props
|
||||||
|
|
@ -99,6 +45,62 @@ export default function Home(props: {
|
||||||
})
|
})
|
||||||
const shouldHighlightEasySetup = easySetupVisited?.value ? String(easySetupVisited.value) !== 'true' : false
|
const shouldHighlightEasySetup = easySetupVisited?.value ? String(easySetupVisited.value) !== 'true' : false
|
||||||
|
|
||||||
|
// Maps is a Core Capability (display_order: 4)
|
||||||
|
const MAPS_ITEM: DashboardItem = {
|
||||||
|
label: t('maps.label'),
|
||||||
|
to: '/maps',
|
||||||
|
target: '',
|
||||||
|
description: t('maps.description'),
|
||||||
|
icon: <IconMapRoute size={48} />,
|
||||||
|
installed: true,
|
||||||
|
displayOrder: 4,
|
||||||
|
poweredBy: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
// System items shown after all apps
|
||||||
|
const SYSTEM_ITEMS: DashboardItem[] = [
|
||||||
|
{
|
||||||
|
label: t('easySetup.label'),
|
||||||
|
to: '/easy-setup',
|
||||||
|
target: '',
|
||||||
|
description: t('easySetup.description'),
|
||||||
|
icon: <IconBolt size={48} />,
|
||||||
|
installed: true,
|
||||||
|
displayOrder: 50,
|
||||||
|
poweredBy: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('installApps.label'),
|
||||||
|
to: '/settings/apps',
|
||||||
|
target: '',
|
||||||
|
description: t('installApps.description'),
|
||||||
|
icon: <IconPlus size={48} />,
|
||||||
|
installed: true,
|
||||||
|
displayOrder: 51,
|
||||||
|
poweredBy: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('docs.label'),
|
||||||
|
to: '/docs/home',
|
||||||
|
target: '',
|
||||||
|
description: t('docs.description'),
|
||||||
|
icon: <IconHelp size={48} />,
|
||||||
|
installed: true,
|
||||||
|
displayOrder: 52,
|
||||||
|
poweredBy: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settingsCard.label'),
|
||||||
|
to: '/settings/system',
|
||||||
|
target: '',
|
||||||
|
description: t('settingsCard.description'),
|
||||||
|
icon: <IconSettings size={48} />,
|
||||||
|
installed: true,
|
||||||
|
displayOrder: 53,
|
||||||
|
poweredBy: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// Add installed services (non-dependency services only)
|
// Add installed services (non-dependency services only)
|
||||||
props.system.services
|
props.system.services
|
||||||
.filter((service) => service.installed && service.ui_location)
|
.filter((service) => service.installed && service.ui_location)
|
||||||
|
|
@ -133,18 +135,18 @@ export default function Home(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<Head title="Command Center" />
|
<Head title={t('title')} />
|
||||||
{
|
{
|
||||||
updateInfo?.updateAvailable && (
|
updateInfo?.updateAvailable && (
|
||||||
<div className='flex justify-center items-center p-4 w-full'>
|
<div className='flex justify-center items-center p-4 w-full'>
|
||||||
<Alert
|
<Alert
|
||||||
title="An update is available for Project N.O.M.A.D.!"
|
title={tCommon('alerts.updateAvailable')}
|
||||||
type="info-inverted"
|
type="info-inverted"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
buttonProps={{
|
buttonProps={{
|
||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
children: 'Go to Settings',
|
children: tCommon('buttons.goToSettings'),
|
||||||
icon: 'IconSettings',
|
icon: 'IconSettings',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.location.href = '/settings/update'
|
window.location.href = '/settings/update'
|
||||||
|
|
@ -156,7 +158,7 @@ export default function Home(props: {
|
||||||
}
|
}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
const isEasySetup = item.label === 'Easy Setup'
|
const isEasySetup = item.to === '/easy-setup'
|
||||||
const shouldHighlight = isEasySetup && shouldHighlightEasySetup
|
const shouldHighlight = isEasySetup && shouldHighlightEasySetup
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -169,7 +171,7 @@ export default function Home(props: {
|
||||||
style={{ animationDuration: '1.5s' }}
|
style={{ animationDuration: '1.5s' }}
|
||||||
></span>
|
></span>
|
||||||
<span className="relative inline-flex items-center rounded-full px-2.5 py-1 bg-desert-orange-light text-xs font-semibold text-white shadow-sm">
|
<span className="relative inline-flex items-center rounded-full px-2.5 py-1 bg-desert-orange-light text-xs font-semibold text-white shadow-sm">
|
||||||
Start here!
|
{t('easySetup.badge')}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Head } from '@inertiajs/react'
|
import { Head } from '@inertiajs/react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import i18n from 'i18next'
|
||||||
import SettingsLayout from '~/layouts/SettingsLayout'
|
import SettingsLayout from '~/layouts/SettingsLayout'
|
||||||
import { SystemInformationResponse } from '../../../types/system'
|
import { SystemInformationResponse } from '../../../types/system'
|
||||||
import { formatBytes } from '~/lib/util'
|
import { formatBytes } from '~/lib/util'
|
||||||
|
|
@ -19,6 +21,8 @@ import { IconCpu, IconDatabase, IconServer, IconDeviceDesktop, IconComponents }
|
||||||
export default function SettingsPage(props: {
|
export default function SettingsPage(props: {
|
||||||
system: { info: SystemInformationResponse | undefined }
|
system: { info: SystemInformationResponse | undefined }
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation('settings')
|
||||||
|
const { t: tCommon } = useTranslation('common')
|
||||||
const { data: info } = useSystemInfo({
|
const { data: info } = useSystemInfo({
|
||||||
initialData: props.system.info,
|
initialData: props.system.info,
|
||||||
})
|
})
|
||||||
|
|
@ -44,7 +48,7 @@ export default function SettingsPage(props: {
|
||||||
const handleForceReinstallOllama = () => {
|
const handleForceReinstallOllama = () => {
|
||||||
openModal(
|
openModal(
|
||||||
<StyledModal
|
<StyledModal
|
||||||
title="Reinstall AI Assistant?"
|
title={tCommon('alerts.reinstallAIConfirmTitle')}
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
closeAllModals()
|
closeAllModals()
|
||||||
setReinstalling(true)
|
setReinstalling(true)
|
||||||
|
|
@ -54,14 +58,14 @@ export default function SettingsPage(props: {
|
||||||
throw new Error(response?.message || 'Force reinstall failed')
|
throw new Error(response?.message || 'Force reinstall failed')
|
||||||
}
|
}
|
||||||
addNotification({
|
addNotification({
|
||||||
message: 'AI Assistant is being reinstalled with GPU support. This page will reload shortly.',
|
message: tCommon('alerts.reinstallSuccess'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
})
|
})
|
||||||
try { localStorage.removeItem('nomad:gpu-banner-dismissed') } catch {}
|
try { localStorage.removeItem('nomad:gpu-banner-dismissed') } catch {}
|
||||||
setTimeout(() => window.location.reload(), 5000)
|
setTimeout(() => window.location.reload(), 5000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addNotification({
|
addNotification({
|
||||||
message: `Failed to reinstall: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
message: tCommon('alerts.reinstallFailed', { error: error instanceof Error ? error.message : 'Unknown error' }),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
setReinstalling(false)
|
setReinstalling(false)
|
||||||
|
|
@ -69,13 +73,11 @@ export default function SettingsPage(props: {
|
||||||
}}
|
}}
|
||||||
onCancel={closeAllModals}
|
onCancel={closeAllModals}
|
||||||
open={true}
|
open={true}
|
||||||
confirmText="Reinstall"
|
confirmText={tCommon('buttons.reinstall')}
|
||||||
cancelText="Cancel"
|
cancelText={tCommon('buttons.cancel')}
|
||||||
>
|
>
|
||||||
<p className="text-text-primary">
|
<p className="text-text-primary">
|
||||||
This will recreate the AI Assistant container with GPU support enabled.
|
{tCommon('alerts.reinstallAIConfirmMessage')}
|
||||||
Your downloaded models will be preserved. The service will be briefly
|
|
||||||
unavailable during reinstall.
|
|
||||||
</p>
|
</p>
|
||||||
</StyledModal>,
|
</StyledModal>,
|
||||||
'gpu-health-force-reinstall-modal'
|
'gpu-health-force-reinstall-modal'
|
||||||
|
|
@ -110,22 +112,22 @@ export default function SettingsPage(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<Head title="System Information" />
|
<Head title={t('system.title')} />
|
||||||
<div className="xl:pl-72 w-full">
|
<div className="xl:pl-72 w-full">
|
||||||
<main className="px-6 lg:px-12 py-6 lg:py-8">
|
<main className="px-6 lg:px-12 py-6 lg:py-8">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-4xl font-bold text-desert-green mb-2">System Information</h1>
|
<h1 className="text-4xl font-bold text-desert-green mb-2">{t('system.title')}</h1>
|
||||||
<p className="text-desert-stone-dark">
|
<p className="text-desert-stone-dark">
|
||||||
Real-time monitoring and diagnostics • Last updated: {new Date().toLocaleString()} •
|
{t('system.subtitle')} • {t('system.lastUpdated', { time: new Date().toLocaleString() })} •
|
||||||
Refreshing data every 30 seconds
|
{' '}{t('system.refreshing')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{Number(memoryUsagePercent) > 90 && (
|
{Number(memoryUsagePercent) > 90 && (
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Alert
|
<Alert
|
||||||
type="error"
|
type="error"
|
||||||
title="Very High Memory Usage Detected"
|
title={tCommon('alerts.highMemoryTitle')}
|
||||||
message="System memory usage exceeds 90%. Performance degradation may occur."
|
message={tCommon('alerts.highMemoryMessage')}
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -133,24 +135,24 @@ export default function SettingsPage(props: {
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||||
<div className="w-1 h-6 bg-desert-green" />
|
<div className="w-1 h-6 bg-desert-green" />
|
||||||
Resource Usage
|
{t('sections.resourceUsage')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||||
<CircularGauge
|
<CircularGauge
|
||||||
value={info?.currentLoad.currentLoad || 0}
|
value={info?.currentLoad.currentLoad || 0}
|
||||||
label="CPU Usage"
|
label={t('labels.cpuUsage')}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="cpu"
|
variant="cpu"
|
||||||
subtext={`${info?.cpu.cores || 0} cores`}
|
subtext={t('labels.cores', { count: info?.cpu.cores || 0 })}
|
||||||
icon={<IconCpu className="w-8 h-8" />}
|
icon={<IconCpu className="w-8 h-8" />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||||
<CircularGauge
|
<CircularGauge
|
||||||
value={Number(memoryUsagePercent)}
|
value={Number(memoryUsagePercent)}
|
||||||
label="Memory Usage"
|
label={t('labels.memoryUsage')}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="memory"
|
variant="memory"
|
||||||
subtext={`${formatBytes(memoryUsed)} / ${formatBytes(info?.mem.total || 0)}`}
|
subtext={`${formatBytes(memoryUsed)} / ${formatBytes(info?.mem.total || 0)}`}
|
||||||
|
|
@ -160,7 +162,7 @@ export default function SettingsPage(props: {
|
||||||
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
<div className="bg-desert-white rounded-lg p-6 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||||
<CircularGauge
|
<CircularGauge
|
||||||
value={Number(swapUsagePercent)}
|
value={Number(swapUsagePercent)}
|
||||||
label="Swap Usage"
|
label={t('labels.swapUsage')}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="disk"
|
variant="disk"
|
||||||
subtext={`${formatBytes(info?.mem.swapused || 0)} / ${formatBytes(info?.mem.swaptotal || 0)}`}
|
subtext={`${formatBytes(info?.mem.swapused || 0)} / ${formatBytes(info?.mem.swaptotal || 0)}`}
|
||||||
|
|
@ -172,7 +174,7 @@ export default function SettingsPage(props: {
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||||
<div className="w-1 h-6 bg-desert-green" />
|
<div className="w-1 h-6 bg-desert-green" />
|
||||||
System Details
|
{t('sections.systemDetails')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
|
@ -181,11 +183,11 @@ export default function SettingsPage(props: {
|
||||||
icon={<IconDeviceDesktop className="w-6 h-6" />}
|
icon={<IconDeviceDesktop className="w-6 h-6" />}
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
data={[
|
data={[
|
||||||
{ label: 'Distribution', value: info?.os.distro },
|
{ label: t('os.distribution'), value: info?.os.distro },
|
||||||
{ label: 'Kernel Version', value: info?.os.kernel },
|
{ label: t('os.kernelVersion'), value: info?.os.kernel },
|
||||||
{ label: 'Architecture', value: info?.os.arch },
|
{ label: t('os.architecture'), value: info?.os.arch },
|
||||||
{ label: 'Hostname', value: info?.os.hostname },
|
{ label: t('os.hostname'), value: info?.os.hostname },
|
||||||
{ label: 'Platform', value: info?.os.platform },
|
{ label: t('os.platform'), value: info?.os.platform },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<InfoCard
|
<InfoCard
|
||||||
|
|
@ -193,13 +195,13 @@ export default function SettingsPage(props: {
|
||||||
icon={<IconCpu className="w-6 h-6" />}
|
icon={<IconCpu className="w-6 h-6" />}
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
data={[
|
data={[
|
||||||
{ label: 'Manufacturer', value: info?.cpu.manufacturer },
|
{ label: t('cpu.manufacturer'), value: info?.cpu.manufacturer },
|
||||||
{ label: 'Brand', value: info?.cpu.brand },
|
{ label: t('cpu.brand'), value: info?.cpu.brand },
|
||||||
{ label: 'Cores', value: info?.cpu.cores },
|
{ label: t('cpu.cores'), value: info?.cpu.cores },
|
||||||
{ label: 'Physical Cores', value: info?.cpu.physicalCores },
|
{ label: t('cpu.physicalCores'), value: info?.cpu.physicalCores },
|
||||||
{
|
{
|
||||||
label: 'Virtualization',
|
label: t('cpu.virtualization'),
|
||||||
value: info?.cpu.virtualization ? 'Enabled' : 'Disabled',
|
value: info?.cpu.virtualization ? t('cpu.enabled') : t('cpu.disabled'),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
@ -208,12 +210,12 @@ export default function SettingsPage(props: {
|
||||||
<Alert
|
<Alert
|
||||||
type="warning"
|
type="warning"
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
title="GPU Not Accessible to AI Assistant"
|
title={tCommon('alerts.gpuNotAccessibleTitle')}
|
||||||
message="Your system has an NVIDIA GPU, but the AI Assistant can't access it. AI is running on CPU only, which is significantly slower."
|
message={tCommon('alerts.gpuNotAccessibleMessage')}
|
||||||
dismissible={true}
|
dismissible={true}
|
||||||
onDismiss={handleDismissGpuBanner}
|
onDismiss={handleDismissGpuBanner}
|
||||||
buttonProps={{
|
buttonProps={{
|
||||||
children: 'Fix: Reinstall AI Assistant',
|
children: tCommon('alerts.fixReinstallAI'),
|
||||||
icon: 'IconRefresh',
|
icon: 'IconRefresh',
|
||||||
variant: 'action',
|
variant: 'action',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
|
|
@ -226,15 +228,15 @@ export default function SettingsPage(props: {
|
||||||
)}
|
)}
|
||||||
{info?.graphics?.controllers && info.graphics.controllers.length > 0 && (
|
{info?.graphics?.controllers && info.graphics.controllers.length > 0 && (
|
||||||
<InfoCard
|
<InfoCard
|
||||||
title="Graphics"
|
title={t('gpu.title')}
|
||||||
icon={<IconComponents className="w-6 h-6" />}
|
icon={<IconComponents className="w-6 h-6" />}
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
data={info.graphics.controllers.map((gpu, i) => {
|
data={info.graphics.controllers.map((gpu, i) => {
|
||||||
const prefix = info.graphics.controllers.length > 1 ? `GPU ${i + 1} ` : ''
|
const prefix = info.graphics.controllers.length > 1 ? `GPU ${i + 1} ` : ''
|
||||||
return [
|
return [
|
||||||
{ label: `${prefix}Model`, value: gpu.model },
|
{ label: `${prefix}${t('gpu.model')}`, value: gpu.model },
|
||||||
{ label: `${prefix}Vendor`, value: gpu.vendor },
|
{ label: `${prefix}${t('gpu.vendor')}`, value: gpu.vendor },
|
||||||
{ label: `${prefix}VRAM`, value: gpu.vram ? `${gpu.vram} MB` : 'N/A' },
|
{ label: `${prefix}${t('gpu.vram')}`, value: gpu.vram ? `${gpu.vram} MB` : 'N/A' },
|
||||||
]
|
]
|
||||||
}).flat()}
|
}).flat()}
|
||||||
/>
|
/>
|
||||||
|
|
@ -244,7 +246,7 @@ export default function SettingsPage(props: {
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||||
<div className="w-1 h-6 bg-desert-green" />
|
<div className="w-1 h-6 bg-desert-green" />
|
||||||
Memory Allocation
|
{t('sections.memoryAllocation')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="bg-desert-white rounded-lg p-8 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
<div className="bg-desert-white rounded-lg p-8 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
|
||||||
|
|
@ -253,7 +255,7 @@ export default function SettingsPage(props: {
|
||||||
{formatBytes(info?.mem.total || 0)}
|
{formatBytes(info?.mem.total || 0)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
||||||
Total RAM
|
{t('labels.totalRam')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|
@ -261,7 +263,7 @@ export default function SettingsPage(props: {
|
||||||
{formatBytes(memoryUsed)}
|
{formatBytes(memoryUsed)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
||||||
Used RAM
|
{t('labels.usedRam')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|
@ -269,7 +271,7 @@ export default function SettingsPage(props: {
|
||||||
{formatBytes(info?.mem.available || 0)}
|
{formatBytes(info?.mem.available || 0)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
<div className="text-sm text-desert-stone-dark uppercase tracking-wide">
|
||||||
Available RAM
|
{t('labels.availableRam')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -280,7 +282,7 @@ export default function SettingsPage(props: {
|
||||||
></div>
|
></div>
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<span className="text-sm font-bold text-white drop-shadow-md z-10">
|
<span className="text-sm font-bold text-white drop-shadow-md z-10">
|
||||||
{memoryUsagePercent}% Utilized
|
{t('labels.utilized', { percent: memoryUsagePercent })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -289,7 +291,7 @@ export default function SettingsPage(props: {
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||||
<div className="w-1 h-6 bg-desert-green" />
|
<div className="w-1 h-6 bg-desert-green" />
|
||||||
Storage Devices
|
{t('sections.storageDevices')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="bg-desert-white rounded-lg p-8 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
<div className="bg-desert-white rounded-lg p-8 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||||
|
|
@ -299,17 +301,17 @@ export default function SettingsPage(props: {
|
||||||
progressiveBarColor={true}
|
progressiveBarColor={true}
|
||||||
statuses={[
|
statuses={[
|
||||||
{
|
{
|
||||||
label: 'Normal',
|
label: t('storage.normal'),
|
||||||
min_threshold: 0,
|
min_threshold: 0,
|
||||||
color_class: 'bg-desert-olive',
|
color_class: 'bg-desert-olive',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Warning - Usage High',
|
label: t('storage.warningHigh'),
|
||||||
min_threshold: 75,
|
min_threshold: 75,
|
||||||
color_class: 'bg-desert-orange',
|
color_class: 'bg-desert-orange',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Critical - Disk Almost Full',
|
label: t('storage.criticalFull'),
|
||||||
min_threshold: 90,
|
min_threshold: 90,
|
||||||
color_class: 'bg-desert-red',
|
color_class: 'bg-desert-red',
|
||||||
},
|
},
|
||||||
|
|
@ -317,7 +319,7 @@ export default function SettingsPage(props: {
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center text-desert-stone-dark py-8">
|
<div className="text-center text-desert-stone-dark py-8">
|
||||||
No storage devices detected
|
{t('labels.noStorageDetected')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -325,12 +327,38 @@ export default function SettingsPage(props: {
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||||
<div className="w-1 h-6 bg-desert-green" />
|
<div className="w-1 h-6 bg-desert-green" />
|
||||||
System Status
|
{t('sections.systemStatus')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<StatusCard title="System Uptime" value={uptimeDisplay} />
|
<StatusCard title={t('labels.systemUptime')} value={uptimeDisplay} />
|
||||||
<StatusCard title="CPU Cores" value={info?.cpu.cores || 0} />
|
<StatusCard title={t('labels.cpuCores')} value={info?.cpu.cores || 0} />
|
||||||
<StatusCard title="Storage Devices" value={storageItems.length} />
|
<StatusCard title={t('labels.storageDevicesCount')} value={storageItems.length} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className="mt-12">
|
||||||
|
<h2 className="text-2xl font-bold text-desert-green mb-6 flex items-center gap-2">
|
||||||
|
<div className="w-1 h-6 bg-desert-green" />
|
||||||
|
{t('sections.preferences')}
|
||||||
|
</h2>
|
||||||
|
<div className="bg-desert-white rounded-lg p-8 border border-desert-stone-light shadow-sm hover:shadow-lg transition-shadow">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-text-primary">{tCommon('language.label')}</h3>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={i18n.language}
|
||||||
|
onChange={(e) => {
|
||||||
|
const lang = e.target.value
|
||||||
|
i18n.changeLanguage(lang)
|
||||||
|
try { localStorage.setItem('nomad:language', lang) } catch {}
|
||||||
|
api.updateSetting('ui.language', lang).catch(() => {})
|
||||||
|
}}
|
||||||
|
className="bg-surface-primary border border-border-default rounded-lg px-4 py-2 text-text-primary focus:outline-none focus:ring-2 focus:ring-desert-green"
|
||||||
|
>
|
||||||
|
<option value="en">{tCommon('language.en')}</option>
|
||||||
|
<option value="pt-BR">{tCommon('language.pt-BR')}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
87
admin/package-lock.json
generated
87
admin/package-lock.json
generated
|
|
@ -46,6 +46,7 @@
|
||||||
"edge.js": "^6.2.1",
|
"edge.js": "^6.2.1",
|
||||||
"fast-xml-parser": "^5.5.6",
|
"fast-xml-parser": "^5.5.6",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
|
"i18next": "^25.10.5",
|
||||||
"luxon": "^3.6.1",
|
"luxon": "^3.6.1",
|
||||||
"maplibre-gl": "^4.7.1",
|
"maplibre-gl": "^4.7.1",
|
||||||
"mysql2": "^3.14.1",
|
"mysql2": "^3.14.1",
|
||||||
|
|
@ -58,6 +59,7 @@
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-adonis-transmit": "^1.0.1",
|
"react-adonis-transmit": "^1.0.1",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-i18next": "^16.6.2",
|
||||||
"react-map-gl": "^8.1.0",
|
"react-map-gl": "^8.1.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
|
|
@ -1092,6 +1094,15 @@
|
||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.29.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||||
|
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
|
|
@ -9646,6 +9657,15 @@
|
||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/html-parse-stringify": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"void-elements": "3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-url-attributes": {
|
"node_modules/html-url-attributes": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||||
|
|
@ -9749,6 +9769,37 @@
|
||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "25.10.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.10.5.tgz",
|
||||||
|
"integrity": "sha512-jRnF7eRNsdcnh7AASSgaU3lj/8lJZuHkfsouetnLEDH0xxE1vVi7qhiJ9RhdSPUyzg4ltb7P7aXsFlTk9sxL2w==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.locize.com/i18next"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.locize.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.29.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.7.2",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||||
|
|
@ -13776,6 +13827,33 @@
|
||||||
"react": "^19.2.4"
|
"react": "^19.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-i18next": {
|
||||||
|
"version": "16.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.6.2.tgz",
|
||||||
|
"integrity": "sha512-/S/GPzElTqEi5o2kzd0/O2627hPDmE6OGhJCCwCfUaQ3syyu+kaYH8/PYFtZeWc25NzfxTN/2fD1QjvrTgrFfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.29.2",
|
||||||
|
"html-parse-stringify": "^3.0.1",
|
||||||
|
"use-sync-external-store": "^1.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"i18next": ">= 25.6.2",
|
||||||
|
"react": ">= 16.8.0",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
|
@ -16234,6 +16312,15 @@
|
||||||
"vite": "^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
"vite": "^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/void-elements": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vt-pbf": {
|
"node_modules/vt-pbf": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@
|
||||||
"edge.js": "^6.2.1",
|
"edge.js": "^6.2.1",
|
||||||
"fast-xml-parser": "^5.5.6",
|
"fast-xml-parser": "^5.5.6",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
|
"i18next": "^25.10.5",
|
||||||
"luxon": "^3.6.1",
|
"luxon": "^3.6.1",
|
||||||
"maplibre-gl": "^4.7.1",
|
"maplibre-gl": "^4.7.1",
|
||||||
"mysql2": "^3.14.1",
|
"mysql2": "^3.14.1",
|
||||||
|
|
@ -110,6 +111,7 @@
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-adonis-transmit": "^1.0.1",
|
"react-adonis-transmit": "^1.0.1",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-i18next": "^16.6.2",
|
||||||
"react-map-gl": "^8.1.0",
|
"react-map-gl": "^8.1.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export const KV_STORE_SCHEMA = {
|
||||||
'ui.theme': 'string',
|
'ui.theme': 'string',
|
||||||
'ai.assistantCustomName': 'string',
|
'ai.assistantCustomName': 'string',
|
||||||
'gpu.type': 'string',
|
'gpu.type': 'string',
|
||||||
|
'ui.language': 'string',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
type KVTagToType<T extends string> = T extends 'boolean' ? boolean : string
|
type KVTagToType<T extends string> = T extends 'boolean' ? boolean : string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user