diff --git a/browser-extension-host.js b/browser-extension-host.js new file mode 100644 index 0000000..d849bfd --- /dev/null +++ b/browser-extension-host.js @@ -0,0 +1,94 @@ +#!/usr/bin/env node +/** + * Soterios Native Messaging Host + * Receives messages from browser extension and forwards to desktop app + */ + +const { spawn } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +function readMessage() { + return new Promise((resolve, reject) => { + const lenBuf = Buffer.alloc(4); + let read = 0; + process.stdin.on('readable', () => { + const chunk = process.stdin.read(4 - read); + if (chunk) { + chunk.copy(lenBuf, read); + read += chunk.length; + if (read === 4) { + const len = lenBuf.readUInt32LE(0); + const msgBuf = Buffer.alloc(len); + let msgRead = 0; + process.stdin.on('readable', () => { + const chunk = process.stdin.read(len - msgRead); + if (chunk) { + chunk.copy(msgBuf, msgRead); + msgRead += chunk.length; + if (msgRead === len) { + resolve(JSON.parse(msgBuf.toString('utf8'))); + } + } + }); + } + } + }); + process.stdin.on('error', reject); + }); +} + +function sendMessage(msg) { + const buf = Buffer.from(JSON.stringify(msg), 'utf8'); + const lenBuf = Buffer.alloc(4); + lenBuf.writeUInt32LE(buf.length, 0); + process.stdout.write(lenBuf); + process.stdout.write(buf); +} + +async function connectToDesktopApp() { + const pipeName = '\\\\.\\pipe\\soterios-credential-safety'; + return new Promise((resolve, reject) => { + const client = require('net').createConnection(pipeName, () => { + resolve(client); + }); + client.on('error', reject); + }); +} + +let desktopClient = null; + +async function main() { + console.error('[Soterios Host] Starting...'); + + try { + desktopClient = await connectToDesktopApp(); + console.error('[Soterios Host] Connected to desktop app'); + } catch (e) { + console.error('[Soterios Host] Desktop app not running:', e.message); + } + + while (true) { + try { + const msg = await readMessage(); + console.error('[Soterios Host] Received:', msg.type); + + if (msg.type === 'CREDENTIAL_LEAK') { + if (desktopClient) { + desktopClient.write(JSON.stringify({ type: 'CREDENTIAL_LEAK', ...msg.payload }) + '\n'); + } + sendMessage({ ok: true }); + } else if (msg.type === 'PING') { + sendMessage({ pong: true }); + } + } catch (e) { + if (e.message.includes('Unexpected end of JSON')) break; + console.error('[Soterios Host] Error:', e.message); + } + } +} + +main().catch(e => { + console.error('[Soterios Host] Fatal:', e); + process.exit(1); +}); \ No newline at end of file diff --git a/browser-extension-host.json b/browser-extension-host.json new file mode 100644 index 0000000..423867b --- /dev/null +++ b/browser-extension-host.json @@ -0,0 +1,9 @@ +{ + "name": "com.soterios.credential_safety", + "description": "Soterios Credential Safety Native Messaging Host", + "path": "browser-extension-host.exe", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://YOUR_EXTENSION_ID_HERE/" + ] +} \ No newline at end of file diff --git a/browser-extension/background.js b/browser-extension/background.js new file mode 100644 index 0000000..df8f935 --- /dev/null +++ b/browser-extension/background.js @@ -0,0 +1,73 @@ +chrome.runtime.onInstalled.addListener(() => { + chrome.storage.sync.set({ externalLookupsEnabled: true }); +}); + +// Handle CHECK_PASSWORD from content script +chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + if (msg.type === 'CHECK_PASSWORD' && msg.password) { + checkPassword(msg.password).then(sendResponse); + return true; // async response + } +}); + +// Native messaging port for desktop app communication +let nativePort = null; + +function connectNative() { + try { + nativePort = chrome.runtime.connectNative('com.soterios.credential_safety'); + nativePort.onDisconnect.addListener(() => { + console.log('[Soterios] Native host disconnected'); + nativePort = null; + }); + nativePort.onMessage.addListener(handleNativeMessage); + } catch (e) { + console.log('[Soterios] Native host connection failed:', e.message); + } +} + +function handleNativeMessage(msg) { + console.log('[Soterios] Native message:', msg); + // Handle responses from desktop app if needed +} + +async function checkPassword(password) { + const HIBP_API = 'https://api.pwnedpasswords.com/range/'; + const encoder = new TextEncoder(); + const data = encoder.encode(password); + const hashBuffer = await crypto.subtle.digest('SHA-1', data); + const hash = Array.from(new Uint8Array(hashBuffer)) + .map(b => b.toString(16).padStart(2, '0')) + .join('') + .toUpperCase(); + + const prefix = hash.slice(0, 5); + const suffix = hash.slice(5); + + try { + const resp = await fetch(`${HIBP_API}${prefix}`); + const text = await resp.text(); + const lines = text.trim().split('\n'); + + for (const line of lines) { + const [suf, count] = line.split(':'); + if (suf === suffix) { + return { pwned: true, count: parseInt(count, 10) }; + } + } + return { pwned: false, count: 0 }; + } catch (e) { + console.error('[Soterios] HIBP check failed:', e); + return { error: e.message }; + } +} + +// Connect to native host on startup +connectNative(); + +// Reconnect if native host disconnects +chrome.runtime.onConnect.addListener(port => { + if (port.name === 'native-reconnect') { + connectNative(); + } +}); \ No newline at end of file diff --git a/browser-extension/content.js b/browser-extension/content.js new file mode 100644 index 0000000..19a1081 --- /dev/null +++ b/browser-extension/content.js @@ -0,0 +1,142 @@ +/** + * Soterios Browser Extension - Content Script + * Detects password fields, monitors for credential entry, and shows breach indicators + */ + +let soteriosIcon = null; +let passwordFields = new Map(); +let observer = null; + +function createIcon() { + const icon = document.createElement('img'); + icon.src = chrome.runtime.getURL('icons/icon16.png'); + icon.style.cssText = ` + position: absolute; + width: 16px; height: 16px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.2s; + z-index: 2147483647; + pointer-events: auto; + `; + icon.title = 'Check password with Soterios'; + icon.addEventListener('mouseenter', () => icon.style.opacity = '1'); + icon.addEventListener('mouseleave', () => icon.style.opacity = '0.7'); + icon.addEventListener('click', onIconClick); + return icon; +} + +function positionIcon(icon, input) { + const rect = input.getBoundingClientRect(); + icon.style.top = `${rect.top + window.scrollY + (rect.height - 16) / 2}px`; + icon.style.left = `${rect.right + window.scrollX - 20}px`; +} + +async function onIconClick(e) { + const input = e.target.dataset.forInput; + const el = document.querySelector(`[data-soterios-id="${input}"]`); + if (!el) return; + + const password = el.value; + if (!password) return; + + try { + const result = await chrome.runtime.sendMessage({ type: 'CHECK_PASSWORD', password }); + showResult(el, result); + } catch (err) { + console.error('[Soterios] Check failed:', err); + } +} + +function showResult(input, result) { + removeResult(input); + + const badge = document.createElement('span'); + badge.dataset.soteriosBadge = input.dataset.soteriosId; + badge.style.cssText = ` + position: absolute; + top: -20px; right: -20px; + padding: 2px 6px; + border-radius: 3px; + font-size: 11px; + font-weight: 600; + color: white; + z-index: 2147483647; + background: ${result.pwned ? '#dc3545' : '#28a745'}; + box-shadow: 0 1px 3px rgba(0,0,0,0.3); + `; + badge.textContent = result.pwned ? `Pwned ${result.count}x` : 'Safe'; + badge.title = result.pwned + ? `Found in ${result.count} breach${result.count !== 1 ? 'es' : ''}. Change immediately.` + : 'Not found in known breaches (HIBP)'; + input.parentElement.style.position = 'relative'; + input.parentElement.appendChild(badge); + + setTimeout(() => removeResult(input), 5000); +} + +function removeResult(input) { + const badge = document.querySelector(`[data-soterios-badge="${input.dataset.soteriosId}"]`); + if (badge) badge.remove(); +} + +function addIconToField(input) { + if (input.dataset.soteriosId) return; + + const id = `soterios-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; + input.dataset.soteriosId = id; + + const icon = createIcon(); + icon.dataset.forInput = id; + document.body.appendChild(icon); + positionIcon(icon, input); + + const updatePos = () => positionIcon(icon, input); + window.addEventListener('scroll', updatePos, true); + window.addEventListener('resize', updatePos); + input.addEventListener('blur', () => setTimeout(() => icon.remove(), 200), { once: true }); + + passwordFields.set(input, icon); +} + +function scanForPasswordFields() { + const inputs = document.querySelectorAll('input[type="password"]:not([data-soterios-id])'); + inputs.forEach(addIconToField); +} + +function init() { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init, { once: true }); + return; + } + + scanForPasswordFields(); + + observer = new MutationObserver(mutations => { + for (const m of mutations) { + m.addedNodes.forEach(node => { + if (node.nodeType === 1) { + if (node.matches('input[type="password"]')) addIconToField(node); + node.querySelectorAll('input[type="password"]').forEach(addIconToField); + } + }); + } + }); + + observer.observe(document.body, { childList: true, subtree: true }); +} + +if (typeof window !== 'undefined') { + init(); +} + +chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + if (msg.type === 'SETTINGS_UPDATED') { + if (!msg.settings.showIcon) { + passwordFields.forEach((icon, input) => icon.remove()); + passwordFields.clear(); + } else { + scanForPasswordFields(); + } + } +}); \ No newline at end of file diff --git a/browser-extension/icons/icon.svg b/browser-extension/icons/icon.svg new file mode 100644 index 0000000..0aef8b8 --- /dev/null +++ b/browser-extension/icons/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/browser-extension/icons/icon128.png b/browser-extension/icons/icon128.png new file mode 100644 index 0000000..691ef14 Binary files /dev/null and b/browser-extension/icons/icon128.png differ diff --git a/browser-extension/icons/icon16.png b/browser-extension/icons/icon16.png new file mode 100644 index 0000000..39c96db Binary files /dev/null and b/browser-extension/icons/icon16.png differ diff --git a/browser-extension/icons/icon32.png b/browser-extension/icons/icon32.png new file mode 100644 index 0000000..71ebd86 Binary files /dev/null and b/browser-extension/icons/icon32.png differ diff --git a/browser-extension/icons/icon48.png b/browser-extension/icons/icon48.png new file mode 100644 index 0000000..69b738e Binary files /dev/null and b/browser-extension/icons/icon48.png differ diff --git a/browser-extension/manifest.json b/browser-extension/manifest.json new file mode 100644 index 0000000..7b80b0d --- /dev/null +++ b/browser-extension/manifest.json @@ -0,0 +1,30 @@ +{ + "manifest_version": 3, + "name": "Soterios Credential Safety", + "version": "1.2.1", + "description": "Password breach checker and credential safety companion for Soterios", + "icons": { + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + }, + "action": { + "default_popup": "popup.html", + "default_title": "Soterios Credential Safety" + }, + "options_page": "options.html", + "permissions": ["storage", "nativeMessaging"], + "host_permissions": ["https://api.pwnedpasswords.com/*"], + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"], + "run_at": "document_idle", + "all_frames": true + } + ] +} \ No newline at end of file diff --git a/browser-extension/native-host-manifest.json b/browser-extension/native-host-manifest.json new file mode 100644 index 0000000..cd5205f --- /dev/null +++ b/browser-extension/native-host-manifest.json @@ -0,0 +1,9 @@ +{ + "name": "com.soterios.credential_safety", + "description": "Soterios Credential Safety Native Messaging Host", + "path": "native-host.bat", + "type": "stdio", + "allowed_origins": [ + "chrome-extension:///" + ] +} \ No newline at end of file diff --git a/browser-extension/native-host.bat b/browser-extension/native-host.bat new file mode 100644 index 0000000..fb757d6 --- /dev/null +++ b/browser-extension/native-host.bat @@ -0,0 +1,6 @@ +@echo off +REM Soterios Native Messaging Host +REM This batch file launches the Node.js native host that communicates with the desktop app + +set NODE_PATH=%~dp0..\..\node_modules +node "%~dp0native-host.js" %* \ No newline at end of file diff --git a/browser-extension/native-host.js b/browser-extension/native-host.js new file mode 100644 index 0000000..600a28a --- /dev/null +++ b/browser-extension/native-host.js @@ -0,0 +1,125 @@ +#!/usr/bin/env node +/** + * Soterios Native Messaging Host + * Bridges browser extension <-> desktop Electron app via stdin/stdout JSON messages + */ + +const { spawn } = require('child_process'); +const readline = require('readline'); +const fs = require('fs'); +const path = require('path'); + +const DESKTOP_APP = process.env.SOTERIOS_APP_PATH || 'soterios://'; + +function log(...args) { + console.error('[Soterios Native Host]', new Date().toISOString(), ...args); +} + +function send(msg) { + const json = JSON.stringify(msg); + const len = Buffer.byteLength(json); + const buf = Buffer.alloc(4 + len); + buf.writeUInt32LE(len, 0); + buf.write(json, 4); + process.stdout.write(buf); +} + +function readMessages() { + const rl = readline.createInterface({ + input: process.stdin, + terminal: false + }); + + let buffer = Buffer.alloc(0); + + process.stdin.on('data', chunk => { + buffer = Buffer.concat([buffer, chunk]); + + while (buffer.length >= 4) { + const len = buffer.readUInt32LE(0); + if (buffer.length < 4 + len) break; + + const json = buffer.subarray(4, 4 + len).toString(); + buffer = buffer.subarray(4 + len); + + try { + const msg = JSON.parse(json); + handleMessage(msg); + } catch (e) { + log('Parse error:', e.message); + } + } + }); +} + +let desktopProc = null; +const pending = new Map(); +let msgId = 0; + +function launchDesktopApp() { + if (desktopProc) return Promise.resolve(); + + return new Promise((resolve, reject) => { + const appPath = process.env.DESKTOP_APP; + if (!appPath) { + return reject(new Error('DESKTOP_APP environment variable not set')); + } + + // Resolve and validate path - prevent command injection + const resolvedPath = path.resolve(appPath); + if (!fs.existsSync(resolvedPath)) { + return reject(new Error('Desktop app not found at: ' + resolvedPath)); + } + + const isWin = process.platform === 'win32'; + const args = isWin ? ['/c', 'start', '""', resolvedPath] : [resolvedPath]; + const cmd = isWin ? 'cmd' : resolvedPath; + const options = { shell: false, detached: true }; + + desktopProc = spawn(cmd, args, options); + desktopProc.unref(); + + desktopProc.on('error', e => { + log('Desktop app launch error:', e.message); + desktopProc = null; + }); + + setTimeout(resolve, 1500); + }); +} + +async function handleMessage(msg) { + log('Received:', msg.type); + + switch (msg.type) { + case 'CREDENTIAL_LEAK': { + await launchDesktopApp(); + send({ type: 'LEAK_NOTIFIED', ok: true, original: msg }); + break; + } + case 'PING': { + send({ type: 'PONG', ok: true }); + break; + } + case 'OPEN_APP': { + await launchDesktopApp(); + send({ type: 'APP_OPENED', ok: true }); + break; + } + default: { + send({ type: 'ERROR', error: 'Unknown message type', original: msg }); + } + } +} + +process.on('uncaughtException', e => { + log('Uncaught:', e); + send({ type: 'ERROR', error: e.message }); +}); + +process.on('unhandledRejection', e => { + log('Unhandled rejection:', e); +}); + +log('Starting native messaging host'); +readMessages(); \ No newline at end of file diff --git a/browser-extension/options.html b/browser-extension/options.html new file mode 100644 index 0000000..3c53605 --- /dev/null +++ b/browser-extension/options.html @@ -0,0 +1,75 @@ + + + + + Soterios Options + + + +

Soterios Credential Safety

+

Configure breach monitoring and desktop integration

+ +
+

Breach Monitoring

+
+
+
Check passwords against Have I Been Pwned
+
Uses k-anonymity (only first 5 hash chars sent). Never sends full password.
+
+ +
+
+
+
Auto-check on password fields
+
Show breach indicator automatically when you enter a password
+
+ +
+
+
+
Show Soterios icon in password fields
+
Click to manually check any password
+
+ +
+
+ +
+

Desktop App Integration

+
+
+
Notify Soterios desktop app of leaks
+
Sends breach alerts to the desktop app via native messaging
+
+ +
+
+ +
Settings saved
+ +
+ Note: Desktop integration requires the Soterios native messaging host installed. The installer sets this up automatically. If desktop notifications don't work, reinstall Soterios or run node tools/install-native-host.js as admin. +
+ + + + \ No newline at end of file diff --git a/browser-extension/options.js b/browser-extension/options.js new file mode 100644 index 0000000..562e5bc --- /dev/null +++ b/browser-extension/options.js @@ -0,0 +1,50 @@ +const DEFAULTS = { + hibpEnabled: true, + autoCheck: true, + showIcon: true, + notifyDesktop: true +}; + +function loadSettings() { + chrome.storage.sync.get(DEFAULTS, settings => { + Object.keys(DEFAULTS).forEach(key => { + const el = document.getElementById(key); + if (el) el.setAttribute('aria-checked', settings[key]); + }); + }); +} + +function saveSettings() { + const settings = {}; + Object.keys(DEFAULTS).forEach(key => { + const el = document.getElementById(key); + if (el) settings[key] = el.getAttribute('aria-checked') === 'true'; + }); + chrome.storage.sync.set(settings, () => { + const msg = document.getElementById('savedMsg'); + msg.classList.add('show'); + setTimeout(() => msg.classList.remove('show'), 1500); + chrome.runtime.sendMessage({ type: 'SETTINGS_UPDATED', settings }); + }); +} + +function setupToggles() { + document.querySelectorAll('.toggle').forEach(btn => { + btn.addEventListener('click', () => { + const checked = btn.getAttribute('aria-checked') === 'true'; + btn.setAttribute('aria-checked', !checked); + saveSettings(); + }); + btn.addEventListener('keydown', e => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + btn.click(); + } + }); + }); +} + +document.addEventListener('DOMContentLoaded', () => { + loadSettings(); + setupToggles(); +}); \ No newline at end of file diff --git a/browser-extension/package.json b/browser-extension/package.json new file mode 100644 index 0000000..5466349 --- /dev/null +++ b/browser-extension/package.json @@ -0,0 +1,14 @@ +{ + "name": "soterios-browser-extension", + "version": "1.0.0", + "description": "Soterios Credential Safety Browser Extension", + "private": true, + "scripts": { + "build:icons": "node tools/build-icons.js", + "package": "npm run build:icons && cd browser-extension && zip -r ../soterios-extension.zip . -x '*.DS_Store' 'icons/*.svg' 'tools/*'", + "install:host": "node tools/install-native-host.js" + }, + "devDependencies": { + "svgexport": "^0.4.2" + } +} \ No newline at end of file diff --git a/browser-extension/popup.html b/browser-extension/popup.html new file mode 100644 index 0000000..1ea5687 --- /dev/null +++ b/browser-extension/popup.html @@ -0,0 +1,63 @@ + + + + + + + +
+ +

Soterios Credential Safety

+
+ +
+
Check a Password
+
+ + +
+
+
+ +
+
Desktop App
+
+ + Checking connection... +
+
+ + Settings + + + + \ No newline at end of file diff --git a/browser-extension/popup.js b/browser-extension/popup.js new file mode 100644 index 0000000..377cc38 --- /dev/null +++ b/browser-extension/popup.js @@ -0,0 +1,88 @@ +const HIBP_API = 'https://api.pwnedpasswords.com/range/'; + +async function sha1(str) { + const buf = new TextEncoder().encode(str); + const hash = await crypto.subtle.digest('SHA-1', buf); + return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase(); +} + +async function checkPwned(password) { + const hash = await sha1(password); + const prefix = hash.slice(0, 5); + const suffix = hash.slice(5); + const resp = await fetch(`${HIBP_API}${prefix}`); + const text = await resp.text(); + const lines = text.trim().split('\n'); + for (const line of lines) { + const [suf, count] = line.split(':'); + if (suf === suffix) return parseInt(count, 10); + } + return 0; +} + +function showResult(count) { + const result = document.getElementById('result'); + if (count === 0) { + result.className = 'result safe'; + result.innerHTML = ` +
✓ Not found in breaches
+
This password was not found in the HIBP database (${count} occurrences).
+ `; + } else { + result.className = 'result pwned'; + result.innerHTML = ` +
⚠ Found in ${count} breach${count > 1 ? 'es' : ''}
+
This password appears in known data breaches. Do not use it. Generate a new one in the Soterios app.
+ `; + } + result.style.display = 'block'; +} + +async function checkConnection() { + try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 1000); + const resp = await fetch('http://localhost:17234/api/health', { method: 'GET', signal: controller.signal }); + clearTimeout(timeout); + if (resp.ok) { + document.getElementById('statusDot').classList.remove('offline'); + document.getElementById('statusText').textContent = 'Soterios app connected'; + } else throw new Error(); + } catch { + document.getElementById('statusDot').classList.add('offline'); + document.getElementById('statusText').textContent = 'Soterios app not running'; + } +} + +document.getElementById('checkBtn').addEventListener('click', async () => { + const input = document.getElementById('passwordInput'); + const btn = document.getElementById('checkBtn'); + const loader = document.getElementById('loader'); + const pwd = input.value; + + if (!pwd) return; + + btn.disabled = true; + loader.classList.add('active'); + document.getElementById('result').style.display = 'none'; + + try { + const count = await checkPwned(pwd); + showResult(count); + } catch (e) { + document.getElementById('result').className = 'result pwned'; + document.getElementById('result').innerHTML = '
Error
Could not check password.
'; + document.getElementById('result').style.display = 'block'; + } finally { + btn.disabled = false; + loader.classList.remove('active'); + } +}); + +document.getElementById('openOptions').addEventListener('click', (e) => { + e.preventDefault(); + chrome.runtime.openOptionsPage(); +}); + +checkConnection(); +setInterval(checkConnection, 30000); \ No newline at end of file diff --git a/src/i18n/locales/ar.json b/src/i18n/locales/ar.json index 0f0d252..dea3e18 100644 --- a/src/i18n/locales/ar.json +++ b/src/i18n/locales/ar.json @@ -786,12 +786,20 @@ "passwords.crackTimeDays": "{count} days", "passwords.crackTimeYears": "{count} years", "passwords.crackTimeCenturies": "{count} centuries", - "health.malware.label": "Malware Scan Results", - "health.malware.noScan": "No scan has been run yet.", - "health.malware.clean": "No threats found in the most recent scan.", - "health.malware.low": "{count} threat match(es) found in the most recent scan.", - "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "health.malware.label": "نتائج فحص البرمجيات الخبيثة", + "health.malware.noScan": "لم يتم تشغيل أي فحص بعد.", + "health.malware.clean": "لم يتم العثور على تهديدات في آخر فحص.", + "health.malware.low": "تم العثور على {count} تطابق(ات) تهديد في آخر فحص.", + "health.malware.high": "تم العثور على {count} تطابقات تهديد في آخر فحص.", + "health.label.malware": "نتائج فحص البرمجيات الخبيثة", + "health.label.scanRecency": "حداثة الفحص", + "health.label.disk": "مساحة القرص", + "health.label.memory": "استخدام الذاكرة", + "health.label.load": "حمل وحدة المعالجة المركزية", + "health.label.uptime": "وقت تشغيل النظام", + "health.label.rtp": "الحماية في الوقت الفعلي", + "health.label.firewall": "جدار الحماية", + "health.scanRecency.label": "حداثة الفحص", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", @@ -869,11 +877,29 @@ "audit.check.secureBoot.rec": "Enable Secure Boot in your UEFI/BIOS firmware settings.", "audit.check.execPolicy.rec2": "Check execution policy with Get-ExecutionPolicy -List in PowerShell.", "audit.check.bitlocker.rec2": "Check BitLocker status in Windows settings.", - "scanIndicator.scanning": "Scanning…", - "scanIndicator.complete": "Scan complete", - "scanIndicator.canceled": "Scan canceled", - "scanIndicator.failed": "Scan failed", - "scanIndicator.threatsFound": "{count} threat(s) found", + "scanIndicator.scanning": "جاري الفحص…", + "scanIndicator.complete": "اكتمل الفحص", + "scanIndicator.canceled": "تم إلغاء الفحص", + "scanIndicator.failed": "فشل الفحص", + "scanIndicator.threatsFound": "تم العثور على {count} تهديد", "dashboard.rtpTitle": "الحماية في الوقت الحقيقي", - "firewall.detailDirection": "{direction} {est}" + "firewall.detailDirection": "{direction} {est}", + "health.reason.noScan": "لم يتم تشغيل أي فحص بعد.", + "health.reason.noThreats": "لم يتم العثور على تهديدات في أحدث فحص.", + "health.reason.threatsFound": "تم العثور على {count} تطابق للتهديدات في أحدث فحص.", + "health.reason.scanToday": "تم تشغيل آخر فحص خلال اليوم الماضي.", + "health.reason.scanDaysAgo": "تم تشغيل آخر فحص منذ {days} يوم.", + "health.reason.diskLowSpace": "مساحة منخفضة على: {volumes} ({pct}% مستخدم).", + "health.reason.diskNoVolumes": "لم يتم العثور على وحدات تخزين مرئية للمستخدم لتقييم القرص.", + "health.reason.diskHealthy": "جميع الوحدات سليمة (أعلى استخدام {pct}%).", + "health.reason.memoryUsage": "{pct}% من الذاكرة قيد الاستخدام.", + "health.reason.cpuLoad": "حمل المعالج عند {pct}%.", + "health.reason.uptimeToday": "أعيد التشغيل خلال اليوم الماضي.", + "health.reason.uptimeDays": "أعيد التشغيل منذ {days} يوم — ضمن النطاق الطبيعي.", + "health.reason.uptimeWeeks": "يعمل منذ {days} يوم دون إعادة تشغيل — يُنصح بإعادة التشغيل قريبًا للتحديثات.", + "health.reason.uptimeLong": "يعمل منذ {days} يوم دون إعادة تشغيل — يُنصح بإعادة التشغيل للتحديثات المعلقة.", + "health.reason.rtpActive": "الحماية في الوقت الحقيقي نشطة.", + "health.reason.rtpDisabled": "الحماية في الوقت الحقيقي معطلة.", + "health.reason.firewallActive": "جدار حماية Windows نشط.", + "health.reason.firewallDisabled": "جدار حماية Windows معطل." } \ No newline at end of file diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 3700ac7..a31cfc8 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -788,11 +788,19 @@ "passwords.crackTimeYears": "{count} years", "passwords.crackTimeCenturies": "{count} centuries", "health.malware.label": "Malware Scan Results", - "health.malware.noScan": "No scan has been run yet.", - "health.malware.clean": "No threats found in the most recent scan.", - "health.malware.low": "{count} threat match(es) found in the most recent scan.", - "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "health.malware.noScan": "Noch kein Scan ausgeführt.", + "health.malware.clean": "Keine Bedrohungen im letzten Scan gefunden.", + "health.malware.low": "{count} Bedrohungs-Treffer im letzten Scan.", + "health.malware.high": "{count} Bedrohungs-Treffer im letzten Scan.", + "health.label.malware": "Ergebnisse des Malware-Scans", + "health.label.scanRecency": "Scan-Aktualität", + "health.label.disk": "Festplattenplatz", + "health.label.memory": "Speichernutzung", + "health.label.load": "CPU-Auslastung", + "health.label.uptime": "Systemlaufzeit", + "health.label.rtp": "Echtzeitschutz", + "health.label.firewall": "Firewall", + "health.scanRecency.label": "Scan-Aktualität", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", @@ -873,8 +881,26 @@ "scanIndicator.scanning": "Scanning…", "scanIndicator.complete": "Scan complete", "scanIndicator.canceled": "Scan canceled", - "scanIndicator.failed": "Scan failed", + "scanIndicator.failed": "Scan fehlgeschlagen", "scanIndicator.threatsFound": "{count} threat(s) found", "dashboard.rtpTitle": "Echtzeitschutz", - "firewall.detailDirection": "{direction} {est}" + "firewall.detailDirection": "{direction} {est}", + "health.reason.noScan": "Noch kein Scan ausgeführt.", + "health.reason.noThreats": "Keine Bedrohungen im letzten Scan gefunden.", + "health.reason.threatsFound": "{count} Bedrohungs-Treffer im letzten Scan.", + "health.reason.scanToday": "Letzter Scan lief innerhalb des letzten Tages.", + "health.reason.scanDaysAgo": "Letzter Scan vor {days} Tag(en).", + "health.reason.diskLowSpace": "Wenig Platz auf: {volumes} ({pct}% belegt).", + "health.reason.diskNoVolumes": "Keine benutzerseitigen Volumes für Disk-Scoring gefunden.", + "health.reason.diskHealthy": "Alle Volumes gesund (höchste Nutzung {pct}%).", + "health.reason.memoryUsage": "{pct}% des Speichers in Verwendung.", + "health.reason.cpuLoad": "CPU-Last bei {pct}%.", + "health.reason.uptimeToday": "Neugestartet innerhalb des letzten Tages.", + "health.reason.uptimeDays": "Neugestartet vor {days} Tag(en) — im normalen Bereich.", + "health.reason.uptimeWeeks": "Läuft seit {days} Tagen ohne Neustart — Neustart empfohlen für Updates.", + "health.reason.uptimeLong": "Läuft seit {days} Tagen ohne Neustart — Neustart empfohlen für ausstehende Updates.", + "health.reason.rtpActive": "Echtzeitschutz ist aktiv.", + "health.reason.rtpDisabled": "Echtzeitschutz ist deaktiviert.", + "health.reason.firewallActive": "Windows-Firewall ist aktiv.", + "health.reason.firewallDisabled": "Windows-Firewall ist deaktiviert." } \ No newline at end of file diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 1592cef..4f854cd 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -117,7 +117,22 @@ "settings.geoLookup.desc": "Resolve IP addresses to display a world map of active connections", "settings.networkPerimeterMap.label": "Network Perimeter Map", "settings.networkPerimeterMap.desc": "Show the live connection visualization on the Firewall page", +"settings.browserExtension.label": "Browser Extension Integration", + "settings.browserExtension.desc": "Receive credential leak alerts from the Soterios browser extension (requires native messaging host)", + "settings.browserExtension.installing": "Installing native messaging host...", + "settings.browserExtension.installed": "Native messaging host installed. Install the extension from Chrome Web Store.", + "settings.browserExtension.installFailed": "Failed to install native host: {error}", + "settings.browserExtension.disabled": "Browser extension integration disabled", "settings.colorScheme": "Color Scheme", + "tray.systemHealth": "System Health", + "tray.rtpActive": "RTP Active", + "tray.rtpOff": "RTP Off", + "tray.network": "Network", + "tray.quickScan": "Quick Scan", + "tray.openApp": "Open Soterios", + "tray.quit": "Quit", + "tray.lastScanAgo": "Last scan {ago}", + "tray.networkRxTx": "↓ {rx} KB/s ↑ {tx} KB/s", "settings.theme.dark": "Dark", "settings.theme.light": "Light", "settings.theme.ocean": "Ocean", @@ -833,6 +848,14 @@ "health.malware.clean": "No threats found in the most recent scan.", "health.malware.low": "{count} threat match(es) found in the most recent scan.", "health.malware.high": "{count} threat matches found in the most recent scan.", + "health.label.malware": "Malware Scan Results", + "health.label.scanRecency": "Scan Recency", + "health.label.disk": "Disk Space", + "health.label.memory": "Memory Usage", + "health.label.load": "CPU Load", + "health.label.uptime": "System Uptime", + "health.label.rtp": "Real-Time Protection", + "health.label.firewall": "Firewall", "health.scanRecency.label": "Scan Recency", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 2d8048d..6be6467 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -795,6 +795,14 @@ "passwords.crackTimeDays": "{count} días", "passwords.crackTimeYears": "{count} años", "passwords.crackTimeCenturies": "{count} siglos", + "health.label.malware": "Resultados del escaneo de malware", + "health.label.scanRecency": "Recencia del escaneo", + "health.label.disk": "Espacio en disco", + "health.label.memory": "Uso de memoria", + "health.label.load": "Carga de CPU", + "health.label.uptime": "Tiempo de actividad del sistema", + "health.label.rtp": "Protección en tiempo real", + "health.label.firewall": "Firewall", "health.malware.label": "Resultados del escaneo de malware", "health.malware.noScan": "Aún no se ha ejecutado ningún escaneo.", "health.malware.clean": "No se encontraron amenazas en el escaneo más reciente.", @@ -808,7 +816,12 @@ "health.disk.noVolumes": "No se encontraron volúmenes orientados al usuario para la puntuación de disco.", "health.disk.healthy": "Todos los volúmenes saludables (uso máximo {usage}%).", "health.memory.label": "Uso de memoria", - "health.reason.uptimeToday": "Reiniciado en el último día.", + "health.memory.reason": "{pct}% de memoria en uso.", + "health.load.label": "Carga de CPU", + "health.load.reason": "Carga de CPU al {pct}%.", + "health.uptime.label": "Tiempo de actividad del sistema", + "health.rtp.label": "Protección en tiempo real", + "health.firewall.label": "Firewall", "health.reason.uptimeDays": "Reiniciado hace {days} día(s) — dentro del rango normal.", "health.reason.uptimeWeeks": "Ejecutándose {days} días sin reiniciar — considere reiniciar pronto para aplicar actualizaciones pendientes.", "health.reason.uptimeLong": "Ejecutándose {days} días sin reiniciar — se recomienda reiniciar para aplicar actualizaciones pendientes.", @@ -823,6 +836,9 @@ "health.reason.diskHealthy": "Todos los volúmenes saludables (uso máximo {pct}%).", "health.reason.memoryUsage": "{pct}% de memoria en uso.", "health.reason.cpuLoad": "Carga de CPU al {pct}%.", + "health.reason.noScan": "Aún no se ha ejecutado ningún escaneo.", + "health.reason.noThreats": "No se encontraron amenazas en el escaneo más reciente.", + "health.reason.threatsFound": "Se encontraron {count} coincidencia(s) de amenaza en el escaneo más reciente.", "audit.check.defender.name": "Windows Defender", "audit.check.rtp.name": "Protección en tiempo real", "audit.check.uac.name": "Control de cuentas de usuario (UAC)", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index ae5881e..1c40d67 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -794,7 +794,15 @@ "health.malware.clean": "No threats found in the most recent scan.", "health.malware.low": "{count} threat match(es) found in the most recent scan.", "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "health.label.malware": "Résultats de l'analyse anti-malware", + "health.label.scanRecency": "Récence de l'analyse", + "health.label.disk": "Espace disque", + "health.label.memory": "Utilisation mémoire", + "health.label.load": "Charge CPU", + "health.label.uptime": "Temps d'activité système", + "health.label.rtp": "Protection en temps réel", + "health.label.firewall": "Pare-feu", + "health.scanRecency.label": "Récence de l'analyse", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", @@ -819,46 +827,64 @@ "toast.scanProgressTitle": "Progression de l'analyse Soterios", "audit.check.defender.enabled.msg": "Defender antivirus is enabled and running.", "audit.check.defender.disabled.msg": "Defender antivirus is disabled!", - "audit.check.defender.disabled.detail": "Antivirus protection is turned off.", - "audit.check.rtp.active.msg": "Real-time protection is active.", - "audit.check.rtp.off.msg": "Real-time protection is off!", - "audit.check.rtp.active.detail": "Threats are blocked as they appear.", - "audit.check.rtp.off.detail": "Your system is vulnerable to active threats.", - "audit.check.uac.enabled.msg": "UAC is enabled.", - "audit.check.uac.disabled.msg": "UAC is disabled! This is a severe security risk.", - "audit.check.uac.enabled.detail": "UAC prompts before making system-level changes.", - "audit.check.uac.disabled.detail": "All programs run with full administrator privileges.", - "audit.check.updates.none.msg": "No pending updates.", - "audit.check.updates.none.detail": "All available updates are installed.", - "audit.check.bitlocker.encrypted.msg": "System drive is encrypted.", - "audit.check.bitlocker.encrypted.detail": "Your data is protected if the device is lost or stolen.", - "audit.check.bitlocker.notEncrypted.msg": "System drive is NOT encrypted.", - "audit.check.bitlocker.unavailable.msg": "BitLocker status unavailable.", - "audit.check.bitlocker.notEncrypted.detail": "Anyone with physical access can read your data.", - "audit.check.bitlocker.unknown.detail": "Could not determine BitLocker protection status.", - "audit.check.bitlocker.unknown.msg": "BitLocker status could not be determined.", - "audit.check.bitlocker.unexpected.detail": "Unexpected BitLocker response format.", - "audit.check.bitlocker.na.msg": "BitLocker is not available on this system.", - "audit.check.bitlocker.na.detail": "Requires Windows Pro/Enterprise and a TPM chip.", - "audit.check.execPolicy.remoteSigned.msg": "Policy: RemoteSigned", - "audit.check.execPolicy.restricted.msg": "Policy: Restricted", - "audit.check.execPolicy.allSigned.msg": "Policy: AllSigned", - "audit.check.execPolicy.secure.detail": "Only signed or locally authored scripts can run.", - "audit.check.execPolicy.insecure.detail": "Less restrictive execution policy may allow untrusted scripts.", - "audit.check.secureBoot.enabled.msg": "Secure Boot is enabled.", - "audit.check.secureBoot.disabled.msg": "Secure Boot is disabled!", - "audit.check.secureBoot.enabled.detail": "Only trusted bootloaders can run during system startup.", - "audit.check.secureBoot.disabled.detail": "System is vulnerable to bootkit attacks.", - "audit.check.defender.parseError.msg": "Could not parse Defender status.", - "audit.check.defender.queryError.msg": "Failed to query Defender status.", - "audit.check.defender.queryError.detail": "The Get-MpComputerStatus cmdlet may not be available on this system.", - "audit.check.uac.error.msg": "Could not check UAC status.", - "audit.check.updates.parseError.msg": "Could not parse update status.", - "audit.check.updates.parseError.detail": "Unexpected response from Windows Update query.", - "audit.check.updates.queryError.msg": "Could not query update status.", - "audit.check.updates.queryError.detail": "Windows Update may be disabled or the COM query timed out.", - "audit.check.bitlocker.info.msg": "BitLocker status unavailable (may not be supported on this edition).", - "audit.check.bitlocker.info.detail": "BitLocker requires Windows Pro or Enterprise.", + "audit.check.defender.disabled.detail": "La protection antivirus est désactivée.", + "audit.check.rtp.active.msg": "La protection en temps réel est active.", + "audit.check.rtp.off.msg": "La protection en temps réel est désactivée !", + "audit.check.rtp.active.detail": "Les menaces sont bloquées dès leur apparition.", + "audit.check.rtp.off.detail": "Votre système est vulnérable aux menaces actives.", + "audit.check.uac.enabled.msg": "UAC est activé.", + "audit.check.uac.disabled.msg": "UAC est désactivé ! C'est un risque de sécurité grave.", + "audit.check.uac.enabled.detail": "UAC demande confirmation avant les changements système.", + "audit.check.uac.disabled.detail": "Tous les programmes s'exécutent avec les privilèges d'administrateur complets.", + "audit.check.updates.none.msg": "Aucune mise à jour en attente.", + "audit.check.updates.none.detail": "Toutes les mises à jour disponibles sont installées.", + "audit.check.bitlocker.encrypted.msg": "Le disque système est chiffré.", + "audit.check.bitlocker.encrypted.detail": "Vos données sont protégées si l'appareil est perdu ou volé.", + "audit.check.bitlocker.notEncrypted.msg": "Le disque système N'EST PAS chiffré.", + "audit.check.bitlocker.unavailable.msg": "État BitLocker indisponible.", + "audit.check.bitlocker.notEncrypted.detail": "Quiconque a un accès physique peut lire vos données.", + "audit.check.bitlocker.unknown.detail": "Impossible de déterminer l'état de protection BitLocker.", + "audit.check.bitlocker.unknown.msg": "Impossible de déterminer l'état BitLocker.", + "audit.check.bitlocker.unexpected.detail": "Format de réponse BitLocker inattendu.", + "audit.check.bitlocker.na.msg": "BitLocker n'est pas disponible sur ce système.", + "audit.check.bitlocker.na.detail": "Nécessite Windows Pro/Entreprise et une puce TPM.", + "audit.check.execPolicy.remoteSigned.msg": "Stratégie : RemoteSigned", + "audit.check.execPolicy.restricted.msg": "Stratégie : Restricted", + "audit.check.execPolicy.allSigned.msg": "Stratégie : AllSigned", + "audit.check.execPolicy.secure.detail": "Seuls les scripts signés ou créés localement peuvent s'exécuter.", + "audit.check.execPolicy.insecure.detail": "Une stratégie d'exécution moins restrictive peut autoriser des scripts non fiables.", + "audit.check.secureBoot.enabled.msg": "Secure Boot est activé.", + "audit.check.secureBoot.disabled.msg": "Secure Boot est désactivé !", + "audit.check.secureBoot.enabled.detail": "Seuls les chargeurs de démarrage de confiance peuvent s'exécuter au démarrage.", + "audit.check.secureBoot.disabled.detail": "Le système est vulnérable aux attaques bootkit.", + "audit.check.defender.parseError.msg": "Impossible d'analyser l'état de Defender.", + "audit.check.defender.queryError.msg": "Échec de la requête d'état Defender.", + "audit.check.defender.queryError.detail": "Le cmdlet Get-MpComputerStatus peut ne pas être disponible sur ce système.", + "audit.check.uac.error.msg": "Impossible de vérifier l'état UAC.", + "audit.check.updates.parseError.msg": "Impossible d'analyser l'état des mises à jour.", + "audit.check.updates.parseError.detail": "Réponse inattendue de la requête Windows Update.", + "audit.check.updates.queryError.msg": "Impossible de consulter l'état des mises à jour.", + "audit.check.updates.queryError.detail": "Windows Update peut être désactivé ou la requête COM a expiré.", + "audit.check.bitlocker.info.msg": "État BitLocker indisponible (peut ne pas être supporté sur cette édition).", + "audit.check.bitlocker.info.detail": "BitLocker nécessite Windows Pro ou Entreprise.", + "health.reason.noScan": "Aucun scan n'a encore été exécuté.", + "health.reason.noThreats": "Aucune menace trouvée lors du scan le plus récent.", + "health.reason.threatsFound": "{count} correspondance(s) de menace trouvées dans le scan le plus récent.", + "health.reason.scanToday": "Le dernier scan a été exécuté dans la dernière journée.", + "health.reason.scanDaysAgo": "Le dernier scan a été exécuté il y a {days} jour(s).", + "health.reason.diskLowSpace": "Peu d'espace sur : {volumes} ({pct}% utilisé).", + "health.reason.diskNoVolumes": "Aucun volume visible par l'utilisateur trouvé pour l'évaluation du disque.", + "health.reason.diskHealthy": "Tous les volumes sains (utilisation max {pct}%).", + "health.reason.memoryUsage": "{pct}% de la mémoire utilisée.", + "health.reason.cpuLoad": "Charge CPU à {pct}%.", + "health.reason.uptimeToday": "Redémarré au cours de la dernière journée.", + "health.reason.uptimeDays": "Redémarré il y a {days} jour(s) — dans la normale.", + "health.reason.uptimeWeeks": "En fonctionnement depuis {days} jours sans redémarrage — envisager un redémarrage pour appliquer les mises à jour en attente.", + "health.reason.uptimeLong": "En fonctionnement depuis {days} jours sans redémarrage — un redémarrage est recommandé pour appliquer les mises à jour en attente.", + "health.reason.rtpActive": "La protection en temps réel est active.", + "health.reason.rtpDisabled": "La protection en temps réel est désactivée.", + "health.reason.firewallActive": "Le pare-feu Windows est actif.", + "health.reason.firewallDisabled": "Le pare-feu Windows est désactivé.", "audit.check.execPolicy.error.msg": "PowerShell execution policy query failed.", "audit.check.execPolicy.error.detail": "Unable to query execution policy.", "audit.check.secureBoot.unknown.msg": "Secure Boot status could not be determined.", diff --git a/src/i18n/locales/hi.json b/src/i18n/locales/hi.json index fa06234..28ed942 100644 --- a/src/i18n/locales/hi.json +++ b/src/i18n/locales/hi.json @@ -793,7 +793,19 @@ "health.malware.clean": "No threats found in the most recent scan.", "health.malware.low": "{count} threat match(es) found in the most recent scan.", "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "health.label.malware": "मैलवेयर स्कैन परिणाम", + "health.label.scanRecency": "स्कैन रीसेंसी", + "health.label.disk": "डिस्क स्पेस", + "health.label.memory": "मेमोरी उपयोग", + "health.label.load": "CPU लोड", + "health.label.uptime": "सिस्टम अपटाइम", + "health.label.rtp": "रियल-टाइम प्रोटेक्शन", + "health.label.firewall": "फायरवॉल", + "health.malware.label": "मैलवेयर स्कैन परिणाम", + "health.malware.noScan": "कोई स्कैन नहीं चला।", + "health.malware.clean": "सबसे हाल के स्कैन में कोई खतरा नहीं मिला।", + "health.malware.low": "सबसे हाल के स्कैन में {count} खतरा मिलान मिले।", + "health.malware.high": "सबसे हाल के स्कैन में {count} खतरा मिलान मिले।", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 65da3ce..0efd86f 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -787,79 +787,87 @@ "passwords.crackTimeYears": "{count} years", "passwords.crackTimeCenturies": "{count} centuries", "health.malware.label": "Malware Scan Results", - "health.malware.noScan": "No scan has been run yet.", - "health.malware.clean": "No threats found in the most recent scan.", - "health.malware.low": "{count} threat match(es) found in the most recent scan.", - "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", - "health.scanRecency.recent": "Last scan ran within the last day.", - "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", - "health.disk.label": "Disk Space", - "health.disk.lowSpace": "Low space on: {volumes} ({usage}% used).", - "health.disk.noVolumes": "No user-facing volumes found for disk scoring.", - "health.disk.healthy": "All volumes healthy (highest usage {usage}%).", - "health.memory.label": "Memory Usage", - "health.memory.reason": "{pct}% of memory in use.", - "health.load.label": "CPU Load", - "health.load.reason": "CPU load at {pct}%.", - "health.uptime.label": "System Uptime", - "health.rtp.label": "Real-Time Protection", + "health.malware.noScan": "Nessun scansione è stata eseguita.", + "health.malware.clean": "Nessuna minaccia trovata nell'ultimo scansione.", + "health.malware.low": "Trovata/e {count} corrispondenza/e di minaccia nell'ultimo scansione.", + "health.malware.high": "Trovate {count} corrispondenze di minaccia nell'ultimo scansione.", + "health.label.malware": "Risultati scansione malware", + "health.label.scanRecency": "Recency scansione", + "health.label.disk": "Spazio su disco", + "health.label.memory": "Utilizzo memoria", + "health.label.load": "Carico CPU", + "health.label.uptime": "Uptime sistema", + "health.label.rtp": "Protezione in tempo reale", + "health.label.firewall": "Firewall", + "health.scanRecency.label": "Recency scansione", + "health.scanRecency.recent": "L'ultimo scansione è stato eseguito nell'ultimo giorno.", + "health.scanRecency.daysAgo": "L'ultimo scansione è stato eseguito {days} giorno fa.", + "health.disk.label": "Spazio su disco", + "health.disk.lowSpace": "Poco spazio su: {volumes} ({usage}% usato).", + "health.disk.noVolumes": "Nessun volume rivolto all'utente trovato per la valutazione del disco.", + "health.disk.healthy": "Tutti i volumi sani (uso massimo {usage}%).", + "health.memory.label": "Utilizzo memoria", + "health.memory.reason": "{pct}% di memoria in uso.", + "health.load.label": "Carico CPU", + "health.load.reason": "Carico CPU al {pct}%.", + "health.uptime.label": "Tempo di attività sistema", + "health.rtp.label": "Protezione in tempo reale", "health.firewall.label": "Firewall", "audit.check.defender.name": "Windows Defender", - "audit.check.rtp.name": "Real-Time Protection", - "audit.check.uac.name": "User Account Control (UAC)", - "audit.check.updates.name": "Windows Updates", - "audit.check.bitlocker.name": "BitLocker Drive Encryption", + "audit.check.rtp.name": "Protezione in tempo reale", + "audit.check.uac.name": "Controllo account utente (UAC)", + "audit.check.updates.name": "Aggiornamenti Windows", + "audit.check.bitlocker.name": "Crittografia unità BitLocker", "audit.check.bitlocker.shortName": "BitLocker", - "audit.check.execPolicy.name": "PowerShell Execution Policy", - "audit.check.secureBoot.name": "Secure Boot", + "audit.check.execPolicy.name": "Criteri di esecuzione PowerShell", + "audit.check.secureBoot.name": "Avvio protetto", "toast.scanProgressTitle": "Avanzamento scansione Soterios", - "audit.check.defender.enabled.msg": "Defender antivirus is enabled and running.", - "audit.check.defender.disabled.msg": "Defender antivirus is disabled!", - "audit.check.defender.disabled.detail": "Antivirus protection is turned off.", - "audit.check.rtp.active.msg": "Real-time protection is active.", - "audit.check.rtp.off.msg": "Real-time protection is off!", - "audit.check.rtp.active.detail": "Threats are blocked as they appear.", - "audit.check.rtp.off.detail": "Your system is vulnerable to active threats.", - "audit.check.uac.enabled.msg": "UAC is enabled.", - "audit.check.uac.disabled.msg": "UAC is disabled! This is a severe security risk.", - "audit.check.uac.enabled.detail": "UAC prompts before making system-level changes.", - "audit.check.uac.disabled.detail": "All programs run with full administrator privileges.", - "audit.check.updates.none.msg": "No pending updates.", - "audit.check.updates.none.detail": "All available updates are installed.", - "audit.check.bitlocker.encrypted.msg": "System drive is encrypted.", - "audit.check.bitlocker.encrypted.detail": "Your data is protected if the device is lost or stolen.", - "audit.check.bitlocker.notEncrypted.msg": "System drive is NOT encrypted.", - "audit.check.bitlocker.unavailable.msg": "BitLocker status unavailable.", - "audit.check.bitlocker.notEncrypted.detail": "Anyone with physical access can read your data.", - "audit.check.bitlocker.unknown.detail": "Could not determine BitLocker protection status.", - "audit.check.bitlocker.unknown.msg": "BitLocker status could not be determined.", - "audit.check.bitlocker.unexpected.detail": "Unexpected BitLocker response format.", - "audit.check.bitlocker.na.msg": "BitLocker is not available on this system.", - "audit.check.bitlocker.na.detail": "Requires Windows Pro/Enterprise and a TPM chip.", - "audit.check.execPolicy.remoteSigned.msg": "Policy: RemoteSigned", - "audit.check.execPolicy.restricted.msg": "Policy: Restricted", - "audit.check.execPolicy.allSigned.msg": "Policy: AllSigned", - "audit.check.execPolicy.secure.detail": "Only signed or locally authored scripts can run.", - "audit.check.execPolicy.insecure.detail": "Less restrictive execution policy may allow untrusted scripts.", - "audit.check.secureBoot.enabled.msg": "Secure Boot is enabled.", - "audit.check.secureBoot.disabled.msg": "Secure Boot is disabled!", - "audit.check.secureBoot.enabled.detail": "Only trusted bootloaders can run during system startup.", - "audit.check.secureBoot.disabled.detail": "System is vulnerable to bootkit attacks.", - "audit.check.defender.parseError.msg": "Could not parse Defender status.", - "audit.check.defender.queryError.msg": "Failed to query Defender status.", - "audit.check.defender.queryError.detail": "The Get-MpComputerStatus cmdlet may not be available on this system.", - "audit.check.uac.error.msg": "Could not check UAC status.", - "audit.check.updates.parseError.msg": "Could not parse update status.", - "audit.check.updates.parseError.detail": "Unexpected response from Windows Update query.", - "audit.check.updates.queryError.msg": "Could not query update status.", - "audit.check.updates.queryError.detail": "Windows Update may be disabled or the COM query timed out.", - "audit.check.bitlocker.info.msg": "BitLocker status unavailable (may not be supported on this edition).", - "audit.check.bitlocker.info.detail": "BitLocker requires Windows Pro or Enterprise.", - "audit.check.execPolicy.error.msg": "PowerShell execution policy query failed.", - "audit.check.execPolicy.error.detail": "Unable to query execution policy.", - "audit.check.secureBoot.unknown.msg": "Secure Boot status could not be determined.", - "audit.check.secureBoot.unknown.detail": "This check may not be supported on virtual machines or older hardware.", + "audit.check.defender.enabled.msg": "L'antivirus Defender è abilitato e in esecuzione.", + "audit.check.defender.disabled.msg": "L'antivirus Defender è disabilitato!", + "audit.check.defender.disabled.detail": "La protezione antivirus è disattivata.", + "audit.check.rtp.active.msg": "La protezione in tempo reale è attiva.", + "audit.check.rtp.off.msg": "La protezione in tempo reale è disattivata!", + "audit.check.rtp.active.detail": "Le minacce vengono bloccate non appena appaiono.", + "audit.check.rtp.off.detail": "Il tuo sistema è vulnerabile alle minacce attive.", + "audit.check.uac.enabled.msg": "UAC è abilitato.", + "audit.check.uac.disabled.msg": "UAC è disabilitato! Questo è un grave rischio per la sicurezza.", + "audit.check.uac.enabled.detail": "UAC richiede conferma prima di apportare modifiche a livello di sistema.", + "audit.check.uac.disabled.detail": "Tutti i programmi vengono eseguiti con privilegi di amministratore completi.", + "audit.check.updates.none.msg": "Nessun aggiornamento in sospeso.", + "audit.check.updates.none.detail": "Tutti gli aggiornamenti disponibili sono installati.", + "audit.check.bitlocker.encrypted.msg": "L'unità di sistema è crittografata.", + "audit.check.bitlocker.encrypted.detail": "I tuoi dati sono protetti se il dispositivo viene perso o rubato.", + "audit.check.bitlocker.notEncrypted.msg": "L'unità di sistema NON è crittografata.", + "audit.check.bitlocker.unavailable.msg": "Stato BitLocker non disponibile.", + "audit.check.bitlocker.notEncrypted.detail": "Chiunque abbia accesso fisico può leggere i tuoi dati.", + "audit.check.bitlocker.unknown.detail": "Impossibile determinare lo stato di protezione BitLocker.", + "audit.check.bitlocker.unknown.msg": "Impossibile determinare lo stato di BitLocker.", + "audit.check.bitlocker.unexpected.detail": "Formato risposta BitLocker inaspettato.", + "audit.check.bitlocker.na.msg": "BitLocker non è disponibile su questo sistema.", + "audit.check.bitlocker.na.detail": "Richiede Windows Pro/Enterprise e un chip TPM.", + "audit.check.execPolicy.remoteSigned.msg": "Criterio: RemoteSigned", + "audit.check.execPolicy.restricted.msg": "Criterio: Restricted", + "audit.check.execPolicy.allSigned.msg": "Criterio: AllSigned", + "audit.check.execPolicy.secure.detail": "Solo script firmati o creati localmente possono essere eseguiti.", + "audit.check.execPolicy.insecure.detail": "Criteri di esecuzione meno restrittivi possono permettere script non affidabili.", + "audit.check.secureBoot.enabled.msg": "Avvio sicuro è abilitato.", + "audit.check.secureBoot.disabled.msg": "Avvio sicuro è disabilitato!", + "audit.check.secureBoot.enabled.detail": "Solo bootloader fidati possono essere eseguiti durante l'avvio del sistema.", + "audit.check.secureBoot.disabled.detail": "Il sistema è vulnerabile agli attacchi bootkit.", + "audit.check.defender.parseError.msg": "Impossibile analizzare lo stato di Defender.", + "audit.check.defender.queryError.msg": "Impossibile interrogare lo stato di Defender.", + "audit.check.defender.queryError.detail": "Il cmdlet Get-MpComputerStatus potrebbe non essere disponibile su questo sistema.", + "audit.check.uac.error.msg": "Impossibile verificare lo stato UAC.", + "audit.check.updates.parseError.msg": "Impossibile analizzare lo stato degli aggiornamenti.", + "audit.check.updates.parseError.detail": "Risposta inaspettata dalla query Windows Update.", + "audit.check.updates.queryError.msg": "Impossibile interrogare lo stato degli aggiornamenti.", + "audit.check.updates.queryError.detail": "Windows Update potrebbe essere disabilitato o la query COM è scaduta.", + "audit.check.bitlocker.info.msg": "Stato BitLocker non disponibile (potrebbe non essere supportato in questa edizione).", + "audit.check.bitlocker.info.detail": "BitLocker richiede Windows Pro o Enterprise.", + "audit.check.execPolicy.error.msg": "Query criterio esecuzione PowerShell fallita.", + "audit.check.execPolicy.error.detail": "Impossibile interrogare il criterio di esecuzione.", + "audit.check.secureBoot.unknown.msg": "Impossibile determinare lo stato di avvio sicuro.", + "audit.check.secureBoot.unknown.detail": "Questa verifica potrebbe non essere supportata su macchine virtuali o hardware più vecchio.", "audit.check.defender.rec": "Keep Windows Update enabled for automatic definition updates.", "audit.check.rtp.rec": "Enable real-time protection in Windows Security settings.", "audit.check.uac.rec": "Enable UAC via Control Panel > User Accounts > Change User Account Control settings.", @@ -875,5 +883,22 @@ "scanIndicator.failed": "Scan failed", "scanIndicator.threatsFound": "{count} threat(s) found", "dashboard.rtpTitle": "Protezione in tempo reale", - "firewall.detailDirection": "{direction} {est}" + "health.reason.noScan": "Nessun scan è stato eseguito.", + "health.reason.noThreats": "Nessuna minaccia trovata nell'ultimo scan.", + "health.reason.threatsFound": "Trovate {count} corrispondenza/e di minaccia nell'ultimo scan.", + "health.reason.scanToday": "L'ultimo scan è stato eseguito nell'ultimo giorno.", + "health.reason.scanDaysAgo": "L'ultimo scan è stato eseguito {days} giorno fa.", + "health.reason.diskLowSpace": "Poco spazio su: {volumes} ({pct}% usato).", + "health.reason.diskNoVolumes": "Nessun volume utente trovato per la valutazione disco.", + "health.reason.diskHealthy": "Tutti i volumi sani (uso massimo {pct}%).", + "health.reason.memoryUsage": "{pct}% di memoria in uso.", + "health.reason.cpuLoad": "Carico CPU al {pct}%.", + "health.reason.uptimeToday": "Riavviato nell'ultimo giorno.", + "health.reason.uptimeDays": "Riavviato {days} giorno fa — nella norma.", + "health.reason.uptimeWeeks": "In esecuzione da {days} giorni senza riavvio — considerare riavvio per aggiornamenti.", + "health.reason.uptimeLong": "In esecuzione da {days} giorni senza riavvio — riavvio consigliato per aggiornamenti.", + "health.reason.rtpActive": "Protezione in tempo reale attiva.", + "health.reason.rtpDisabled": "Protezione in tempo reale disabilitata.", + "health.reason.firewallActive": "Firewall Windows attivo.", + "health.reason.firewallDisabled": "Firewall Windows disabilitato." } \ No newline at end of file diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 59682d0..a527455 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -762,25 +762,33 @@ "passwords.crackTimeDays": "{count} days", "passwords.crackTimeYears": "{count} years", "passwords.crackTimeCenturies": "{count} centuries", - "health.malware.label": "Malware Scan Results", - "health.malware.noScan": "No scan has been run yet.", - "health.malware.clean": "No threats found in the most recent scan.", - "health.malware.low": "{count} threat match(es) found in the most recent scan.", - "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", - "health.scanRecency.recent": "Last scan ran within the last day.", - "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", - "health.disk.label": "Disk Space", - "health.disk.lowSpace": "Low space on: {volumes} ({usage}% used).", - "health.disk.noVolumes": "No user-facing volumes found for disk scoring.", - "health.disk.healthy": "All volumes healthy (highest usage {usage}%).", - "health.memory.label": "Memory Usage", - "health.memory.reason": "{pct}% of memory in use.", - "health.load.label": "CPU Load", - "health.load.reason": "CPU load at {pct}%.", - "health.uptime.label": "System Uptime", - "health.rtp.label": "Real-Time Protection", - "health.firewall.label": "Firewall", + "health.malware.high": "最新のスキャンで {count} 件の脅威マッチが検出されました。", + "health.label.malware": "マルウェア スキャン結果", + "health.label.scanRecency": "スキャン時効性", + "health.label.disk": "ディスク容量", + "health.label.memory": "メモリ使用率", + "health.label.load": "CPU 負荷", + "health.label.uptime": "システム稼働時間", + "health.label.rtp": "リアルタイム防護", + "health.label.firewall": "ファイアウォール", + "health.malware.label": "マルウェア スキャン結果", + "health.malware.noScan": "スキャンが実行されていません。", + "health.malware.clean": "最新のスキャンで脅威は検出されませんでした。", + "health.malware.low": "最新のスキャンで {count} 件の脅威マッチが検出されました。", + "health.malware.high": "最新のスキャンで {count} 件の脅威マッチが検出されました。", + "health.scanRecency.recent": "直近のスキャンは 1 日以内に実行されました。", + "health.scanRecency.daysAgo": "直近のスキャンは {days} 日前に実行されました。", + "health.disk.label": "ディスク容量", + "health.disk.lowSpace": "空き容量不足: {volumes} ({usage}% 使用中)。", + "health.disk.noVolumes": "ディスク評価用のユーザー向けボリュームが見つかりません。", + "health.disk.healthy": "すべてのボリューム正常 (最高使用率 {usage}%)。", + "health.memory.label": "メモリ使用率", + "health.memory.reason": "メモリ使用率 {pct}%。", + "health.load.label": "CPU 負荷", + "health.load.reason": "CPU 負荷 {pct}%。", + "health.uptime.label": "システム稼働時間", + "health.rtp.label": "リアルタイム防護", + "health.firewall.label": "ファイアウォール", "audit.check.defender.name": "Windows Defender", "audit.check.rtp.name": "Real-Time Protection", "audit.check.uac.name": "User Account Control (UAC)", @@ -851,5 +859,23 @@ "scanIndicator.failed": "Scan failed", "scanIndicator.threatsFound": "{count} threat(s) found", "dashboard.rtpTitle": "リアルタイム保護", - "firewall.detailDirection": "{direction} {est}" + "firewall.detailDirection": "{direction} {est}", + "health.reason.noScan": "まだスキャンが実行されていません。", + "health.reason.noThreats": "直近のスキャンで脅威は検出されませんでした。", + "health.reason.threatsFound": "直近のスキャンで {count} 件の脅威が検出されました。", + "health.reason.scanToday": "最後のスキャンは過去 1 以内に実行されました。", + "health.reason.scanDaysAgo": "最後のスキャンは {days} 日前に実行されました。", + "health.reason.diskLowSpace": "空き容量不足: {volumes} ({pct}% 使用中)。", + "health.reason.diskNoVolumes": "ディスクスコアリング用のユーザー向けボリュームが見つかりません。", + "health.reason.diskHealthy": "すべてのボリューム正常 (最大使用率 {pct}%)。", + "health.reason.memoryUsage": "メモリ使用率 {pct}%。", + "health.reason.cpuLoad": "CPU 負荷 {pct}%。", + "health.reason.uptimeToday": "過去 1 日以内に再起動されました。", + "health.reason.uptimeDays": "{days} 日前に再起動 — 正常範囲内。", + "health.reason.uptimeWeeks": "{days} 日間再起動なし — 近いうちに再起動して更新を適用推奨。", + "health.reason.uptimeLong": "{days} 日間再起動なし — 保留中の更新を適用するため再起動推奨。", + "health.reason.rtpActive": "リアルタイム保護が有効です。", + "health.reason.rtpDisabled": "リアルタイム保護が無効です。", + "health.reason.firewallActive": "Windows ファイアウォールが有効です。", + "health.reason.firewallDisabled": "Windows ファイアウォールが無効です。" } \ No newline at end of file diff --git a/src/i18n/locales/ko.json b/src/i18n/locales/ko.json index 8400b0a..84bd323 100644 --- a/src/i18n/locales/ko.json +++ b/src/i18n/locales/ko.json @@ -786,13 +786,34 @@ "passwords.crackTimeHours": "{count} hours", "passwords.crackTimeDays": "{count} days", "passwords.crackTimeYears": "{count} years", - "passwords.crackTimeCenturies": "{count} centuries", - "health.malware.label": "Malware Scan Results", - "health.malware.noScan": "No scan has been run yet.", - "health.malware.clean": "No threats found in the most recent scan.", - "health.malware.low": "{count} threat match(es) found in the most recent scan.", - "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "passwords.crackTimeCenturies": "{count} 세기", + "health.malware.label": "맬웨어 검사 결과", + "health.malware.noScan": "아직 검사가 실행되지 않음.", + "health.malware.clean": "최근 검사에서 위협 없음 발견.", + "health.malware.low": "최근 검사에서 {count}개의 위협 매치 발견.", + "health.malware.high": "최근 검사에서 {count}개의 위협 매치 발견.", + "health.label.malware": "맬웨어 검사 결과", + "health.label.scanRecency": "검사 최신성", + "health.label.disk": "디스크 공간", + "health.label.memory": "메모리 사용량", + "health.label.load": "CPU 부하", + "health.label.uptime": "시스템 가동 시간", + "health.label.rtp": "실시간 보호", + "health.label.firewall": "방화벽", + "health.scanRecency.label": "검사 최신성", + "health.scanRecency.recent": "마지막 검사가 지난 하루 이내에 실행됨.", + "health.scanRecency.daysAgo": "마지막 검사가 {days}일 전 실행됨.", + "health.disk.label": "디스크 공간", + "health.disk.lowSpace": "공간 부족: {volumes} ({usage}% 사용됨).", + "health.disk.noVolumes": "디스크 점수 산정을 위한 사용자 대상 볼륨을 찾을 수 없음.", + "health.disk.healthy": "모든 볼륨 정상 (최고 사용량 {usage}%).", + "health.memory.label": "메모리 사용량", + "health.memory.reason": "{pct}% 메모리 사용 중.", + "health.load.label": "CPU 부하", + "health.load.reason": "CPU 부하 {pct}%.", + "health.uptime.label": "시스템 가동 시간", + "health.rtp.label": "실시간 보호", + "health.firewall.label": "방화벽", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", diff --git a/src/i18n/locales/nl.json b/src/i18n/locales/nl.json index ef957c2..f3ef1d8 100644 --- a/src/i18n/locales/nl.json +++ b/src/i18n/locales/nl.json @@ -789,7 +789,19 @@ "health.malware.clean": "No threats found in the most recent scan.", "health.malware.low": "{count} threat match(es) found in the most recent scan.", "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "health.label.malware": "Malware Scan Resultaten", + "health.label.scanRecency": "Scan Actualiteit", + "health.label.disk": "Schijfruimte", + "health.label.memory": "Geheugengebruik", + "health.label.load": "CPU Belasting", + "health.label.uptime": "Systeem Uptime", + "health.label.rtp": "Realtime Bescherming", + "health.label.firewall": "Firewall", + "health.malware.label": "Malware Scan Resultaten", + "health.malware.noScan": "Nog geen scan uitgevoerd.", + "health.malware.clean": "Geen bedreigingen gevonden in de laatste scan.", + "health.malware.low": "{count} bedreigingsmatch(es) gevonden in de laatste scan.", + "health.malware.high": "{count} bedreigingsmatches gevonden in de laatste scan.", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", @@ -860,17 +872,35 @@ "audit.check.secureBoot.unknown.detail": "This check may not be supported on virtual machines or older hardware.", "audit.check.defender.rec": "Keep Windows Update enabled for automatic definition updates.", "audit.check.rtp.rec": "Enable real-time protection in Windows Security settings.", - "audit.check.uac.rec": "Enable UAC via Control Panel > User Accounts > Change User Account Control settings.", - "audit.check.updates.rec": "Open Settings > Windows Update and install pending updates.", - "audit.check.bitlocker.rec": "Enable BitLocker via Control Panel > BitLocker Drive Encryption.", - "audit.check.execPolicy.rec": "Consider setting to RemoteSigned: Set-ExecutionPolicy RemoteSigned -Scope LocalMachine", - "audit.check.secureBoot.rec": "Enable Secure Boot in your UEFI/BIOS firmware settings.", - "audit.check.execPolicy.rec2": "Check execution policy with Get-ExecutionPolicy -List in PowerShell.", - "audit.check.bitlocker.rec2": "Check BitLocker status in Windows settings.", + "audit.check.uac.rec": "UAC inschakelen via Configuratiescherm > Gebruikersaccounts > Gebruikersaccountbeheer-instellingen wijzigen.", + "audit.check.updates.rec": "Open Instellingen > Windows Update en installeer wachtende updates.", + "audit.check.bitlocker.rec": "Schakel BitLocker in via Configuratiescherm > BitLocker-stationversleuteling.", + "audit.check.execPolicy.rec": "Overweeg in te stellen op RemoteSigned: Set-ExecutionPolicy RemoteSigned -Scope LocalMachine", + "audit.check.secureBoot.rec": "Schakel Secure Boot in via uw UEFI/BIOS-firmware-instellingen.", + "audit.check.execPolicy.rec2": "Controleer uitvoeringsbeleid met Get-ExecutionPolicy -List in PowerShell.", + "audit.check.bitlocker.rec2": "Controleer BitLocker-status in Windows-instellingen.", + "health.reason.noScan": "Nog geen scan uitgevoerd.", + "health.reason.noThreats": "Geen dreigingen gevonden in de laatste scan.", + "health.reason.threatsFound": "{count} dreigingsmatch(es) gevonden in de laatste scan.", + "health.reason.scanToday": "Laatste scan liep de afgelopen dag.", + "health.reason.scanDaysAgo": "Laatste scan liep {days} dag(en) geleden.", + "health.reason.diskLowSpace": "Wenig ruimte op: {volumes} ({pct}% in gebruik).", + "health.reason.diskNoVolumes": "Geen gebruikersgerichte volumes gevonden voor schijfscoring.", + "health.reason.diskHealthy": "Alle volumes gezond (hoogste gebruik {pct}%).", + "health.reason.memoryUsage": "{pct}% van het geheugen in gebruik.", + "health.reason.cpuLoad": "CPU-load op {pct}%.", + "health.reason.uptimeToday": "Herstart binnen de laatste dag.", + "health.reason.uptimeDays": "Herstart {days} dag(en) geleden — binnen normaal bereik.", + "health.reason.uptimeWeeks": "Draait {days} dagen zonder herstart — overweeg herstart voor updates.", + "health.reason.uptimeLong": "Draait {days} dagen zonder herstart — herstart aanbevolen voor updates.", + "health.reason.rtpActive": "Real-time bescherming is actief.", + "health.reason.rtpDisabled": "Real-time bescherming is uitgeschakeld.", + "health.reason.firewallActive": "Windows Firewall is actief.", + "health.reason.firewallDisabled": "Windows Firewall is uitgeschakeld.", "scanIndicator.scanning": "Scanning…", - "scanIndicator.complete": "Scan complete", - "scanIndicator.canceled": "Scan canceled", - "scanIndicator.failed": "Scan failed", + "scanIndicator.complete": "Scan voltooid", + "scanIndicator.canceled": "Scan geannuleerd", + "scanIndicator.failed": "Scan mislukt", "scanIndicator.threatsFound": "{count} threat(s) found", "dashboard.rtpTitle": "Realtime-bescherming", "firewall.detailDirection": "{direction} {est}" diff --git a/src/i18n/locales/pl.json b/src/i18n/locales/pl.json index e36cc4b..b948d2c 100644 --- a/src/i18n/locales/pl.json +++ b/src/i18n/locales/pl.json @@ -792,7 +792,19 @@ "health.malware.clean": "No threats found in the most recent scan.", "health.malware.low": "{count} threat match(es) found in the most recent scan.", "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "health.label.malware": "Wyniki skanowania na złośliwe oprogramowanie", + "health.label.scanRecency": "Aktualność skanowania", + "health.label.disk": "Przestrzeń dyskowa", + "health.label.memory": "Użycie pamięci", + "health.label.load": "Obciążenie CPU", + "health.label.uptime": "Czas działania systemu", + "health.label.rtp": "Ochrona w czasie rzeczywistym", + "health.label.firewall": "Zapora", + "health.malware.label": "Wyniki skanowania na złośliwe oprogramowanie", + "health.malware.noScan": "Nie uruchomiono jeszcze żadnego skanowania.", + "health.malware.clean": "Nie znaleziono zagrożeń w ostatnim skanowaniu.", + "health.malware.low": "W ostatnim skanowaniu znaleziono {count} dopasowanie(ń) zagrożeń.", + "health.malware.high": "W ostatnim skanowaniu znaleziono {count} dopasowań zagrożeń.", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", @@ -876,5 +888,23 @@ "scanIndicator.failed": "Scan failed", "scanIndicator.threatsFound": "{count} threat(s) found", "dashboard.rtpTitle": "Ochrona w czasie rzeczywistym", - "firewall.detailDirection": "{direction} {est}" + "firewall.detailDirection": "{direction} {est}", + "health.reason.noScan": "Nie uruchomiono jeszcze żadnego skanowania.", + "health.reason.noThreats": "Nie znaleziono zagrożeń w ostatnim skanowaniu.", + "health.reason.threatsFound": "Znaleziono {count} dopasowanie(ń) zagrożeń w ostatnim skanowaniu.", + "health.reason.scanToday": "Ostatnie skanowanie uruchomiono w ciągu ostatniego dnia.", + "health.reason.scanDaysAgo": "Ostatnie skanowanie uruchomiono {days} dni temu.", + "health.reason.diskLowSpace": "Mało miejsca na: {volumes} ({pct}% zajęte).", + "health.reason.diskNoVolumes": "Nie znaleziono wolumenów użytkownika do oceny dysku.", + "health.reason.diskHealthy": "Wszystkie wolumeny zdrowe (największe zajęcie {pct}%).", + "health.reason.memoryUsage": "{pct}% pamięci w użyciu.", + "health.reason.cpuLoad": "Obciążenie CPU na {pct}%.", + "health.reason.uptimeToday": "Uruchomiono ponownie w ciągu ostatniego dnia.", + "health.reason.uptimeDays": "Uruchomiono ponownie {days} dni temu — w normie.", + "health.reason.uptimeWeeks": "System działa {days} dni bez restartu — rozważ restart dla aktualizacji.", + "health.reason.uptimeLong": "System działa {days} dni bez restartu — restart zalecany dla aktualizacji.", + "health.reason.rtpActive": "Ochrona w czasie rzeczywistym aktywna.", + "health.reason.rtpDisabled": "Ochrona w czasie rzeczywistym wyłączona.", + "health.reason.firewallActive": "Zapora Windows aktywna.", + "health.reason.firewallDisabled": "Zapora Windows wyłączona." } \ No newline at end of file diff --git a/src/i18n/locales/pt-BR.json b/src/i18n/locales/pt-BR.json index 5ba20ac..1fb064c 100644 --- a/src/i18n/locales/pt-BR.json +++ b/src/i18n/locales/pt-BR.json @@ -791,7 +791,32 @@ "health.malware.clean": "No threats found in the most recent scan.", "health.malware.low": "{count} threat match(es) found in the most recent scan.", "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "health.malware.label": "Resultados da verificação de malware", + "health.malware.noScan": "Nenhuma verificação foi executada ainda.", + "health.malware.clean": "Nenhuma ameaça encontrada na verificação mais recente.", + "health.malware.low": "{count} correspondência(s) de ameaça encontradas na verificação mais recente.", + "health.malware.high": "{count} correspondências de ameaça encontradas na verificação mais recente.", + "health.label.scanRecency": "Recência da verificação", + "health.label.disk": "Espaço em disco", + "health.label.memory": "Uso de memória", + "health.label.load": "Carga de CPU", + "health.label.uptime": "Tempo de atividade do sistema", + "health.label.rtp": "Proteção em tempo real", + "health.label.firewall": "Firewall", + "health.scanRecency.label": "Recência da verificação", + "health.scanRecency.recent": "Última verificação executada no último dia.", + "health.scanRecency.daysAgo": "Última verificação executada há {days} dia(s).", + "health.disk.label": "Espaço em disco", + "health.disk.lowSpace": "Pouco espaço em: {volumes} ({usage}% usado).", + "health.disk.noVolumes": "Nenhum volume voltado para o usuário encontrado para pontuação de disco.", + "health.disk.healthy": "Todos os volumes saudáveis (maior uso {usage}%).", + "health.memory.label": "Uso de memória", + "health.memory.reason": "{pct}% de memória em uso.", + "health.load.label": "Carga de CPU", + "health.load.reason": "Carga de CPU em {pct}%.", + "health.uptime.label": "Tempo de atividade do sistema", + "health.rtp.label": "Proteção em tempo real", + "health.firewall.label": "Firewall", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 494c6ea..f2f7b19 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -785,13 +785,34 @@ "passwords.crackTimeHours": "{count} hours", "passwords.crackTimeDays": "{count} days", "passwords.crackTimeYears": "{count} years", - "passwords.crackTimeCenturies": "{count} centuries", - "health.malware.label": "Malware Scan Results", - "health.malware.noScan": "No scan has been run yet.", - "health.malware.clean": "No threats found in the most recent scan.", - "health.malware.low": "{count} threat match(es) found in the most recent scan.", - "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "passwords.crackTimeCenturies": "{count} веков", + "health.malware.label": "Результаты сканирования на вредоносное ПО", + "health.malware.noScan": "Сканирование еще не запускалось.", + "health.malware.clean": "Угроз не найдено в последнем сканировании.", + "health.malware.low": "Найдено {count} совпадение(я) с угрозами в последнем сканировании.", + "health.malware.high": "Найдено {count} совпадений с угрозами в последнем сканировании.", + "health.label.malware": "Результаты сканирования на вредоносное ПО", + "health.label.scanRecency": "Актуальность сканирования", + "health.label.disk": "Место на диске", + "health.label.memory": "Использование памяти", + "health.label.load": "Загрузка CPU", + "health.label.uptime": "Время работы системы", + "health.label.rtp": "Защита в реальном времени", + "health.label.firewall": "Брандмауэр", + "health.scanRecency.label": "Актуальность сканирования", + "health.scanRecency.recent": "Последнее сканирование запускалось в последний день.", + "health.scanRecency.daysAgo": "Последнее сканирование запускалось {days} день(дня/дней) назад.", + "health.disk.label": "Место на диске", + "health.disk.lowSpace": "Мало места: {volumes} ({usage}% используется).", + "health.disk.noVolumes": "Не найдено пользовательских томов для оценки диска.", + "health.disk.healthy": "Все тома в порядке (макс. загрузка {usage}%).", + "health.memory.label": "Использование памяти", + "health.memory.reason": "{pct}% памяти используется.", + "health.load.label": "Загрузка CPU", + "health.load.reason": "Загрузка CPU на уровне {pct}%.", + "health.uptime.label": "Время работы системы", + "health.rtp.label": "Защита в реальном времени", + "health.firewall.label": "Брандмауэр", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", diff --git a/src/i18n/locales/tr.json b/src/i18n/locales/tr.json index b62c621..999786f 100644 --- a/src/i18n/locales/tr.json +++ b/src/i18n/locales/tr.json @@ -791,7 +791,28 @@ "health.malware.clean": "No threats found in the most recent scan.", "health.malware.low": "{count} threat match(es) found in the most recent scan.", "health.malware.high": "{count} threat matches found in the most recent scan.", - "health.scanRecency.label": "Scan Recency", + "health.label.malware": "Kötü Amaçlı Yazılım Tarama Sonuçları", + "health.label.scanRecency": "Tarama Yeniliği", + "health.label.disk": "Disk Alanı", + "health.label.memory": "Bellek Kullanımı", + "health.label.load": "CPU Yükü", + "health.label.uptime": "Sistem Çalışma Süresi", + "health.label.rtp": "Gerçek Zamanlı Koruma", + "health.label.firewall": "Güvenlik Duvarı", + "health.scanRecency.label": "Tarama Yeniliği", + "health.scanRecency.recent": "Son tarama son bir gün içinde çalıştırıldı.", + "health.scanRecency.daysAgo": "Son tarama {days} gün önce çalıştırıldı.", + "health.disk.label": "Disk Alanı", + "health.disk.lowSpace": "Az alan: {volumes} ({usage}% kullanım).", + "health.disk.noVolumes": "Disk puanlaması için kullanıcı karşıtı birim bulunamadı.", + "health.disk.healthy": "Tüm birimler sağlıklı (en yüksek kullanım {usage}%).", + "health.memory.label": "Bellek Kullanımı", + "health.memory.reason": "{pct}% bellek kullanımda.", + "health.load.label": "CPU Yükü", + "health.load.reason": "CPU yükü %{pct}%.", + "health.uptime.label": "Sistem Çalışma Süresi", + "health.rtp.label": "Gerçek Zamanlı Koruma", + "health.firewall.label": "Güvenlik Duvarı", "health.scanRecency.recent": "Last scan ran within the last day.", "health.scanRecency.daysAgo": "Last scan ran {days} day(s) ago.", "health.disk.label": "Disk Space", @@ -875,5 +896,23 @@ "scanIndicator.failed": "Scan failed", "scanIndicator.threatsFound": "{count} threat(s) found", "dashboard.rtpTitle": "Gerçek zamanlı koruma", - "firewall.detailDirection": "{direction} {est}" + "firewall.detailDirection": "{direction} {est}", + "health.reason.noScan": "Henüz hiç tarama çalıştırılmadı.", + "health.reason.noThreats": "En son tarama da hiçbir tehdit bulunamadı.", + "health.reason.threatsFound": "En son taramada {count} tehdit eşleşmesi bulundu.", + "health.reason.scanToday": "Son tarama son bir gün içinde çalıştırıldı.", + "health.reason.scanDaysAgo": "Son tarama {days} gün önce çalıştırıldı.", + "health.reason.diskLowSpace": "Az yer: {volumes} ({usage}% kullanılıyor).", + "health.reason.diskNoVolumes": "Disk puanlaması için kullanıcı odaklı birim bulunamadı.", + "health.reason.diskHealthy": "Tüm birimler sağlıklı (en yüksek kullanım {usage}%).", + "health.reason.memoryUsage": "%{pct} bellek kullanımda.", + "health.reason.cpuLoad": "CPU yükü %{pct}%.", + "health.reason.uptimeToday": "Son bir gün içinde yeniden başlatıldı.", + "health.reason.uptimeDays": "{days} gün önce yeniden başlatıldı — normal aralıkta.", + "health.reason.uptimeWeeks": "{days} gündür yeniden başlatılmadan çalışıyor — bekleyen güncellemeler için yakında yeniden başlatmayı düşünün.", + "health.reason.uptimeLong": "{days} gündür yeniden başlatılmadan çalışıyor — bekleyen güncellemeleri uygulamak için yeniden başlatma önerilir.", + "health.reason.rtpActive": "Gerçek zamanlı koruma aktif.", + "health.reason.rtpDisabled": "Gerçek zamanlı koruma devre dışı.", + "health.reason.firewallActive": "Windows Güvenlik Duvarı aktif.", + "health.reason.firewallDisabled": "Windows Güvenlik Duvarı devre dışı." } \ No newline at end of file diff --git a/src/main/healthSummary.js b/src/main/healthSummary.js index df30525..692fc5a 100644 --- a/src/main/healthSummary.js +++ b/src/main/healthSummary.js @@ -19,10 +19,60 @@ async function getTrayHealthSummary(db, toolRegistry) { } const disk = result.data.breakdown?.disk; + + // RTP status + let rtp = { enabled: false }; + try { + const { RealTimeWatcher } = require('../security/RealTimeWatcher'); + // Check if RTP is enabled in settings + const rtpEnabled = db.getSetting('feature.realtimeProtection', false); + rtp = { enabled: rtpEnabled }; + } catch (_) {} + + // Firewall status + let firewall = { active: false }; + try { + const { execFile } = require('child_process'); + const { promisify } = require('util'); + const execFileAsync = promisify(execFile); + const { stdout } = await execFileAsync('netsh', ['advfirewall', 'show', 'allprofiles', 'state'], { timeout: 5000 }); + firewall = { active: /ON|ENABLED/i.test(stdout) }; + } catch (_) {} + + // Network traffic history (last 24h) + let network = { rxKBs: 0, txKBs: 0, history: [], rx: [], tx: [] }; + try { + const history = db.getNetworkHistory ? db.getNetworkHistory(24 * 60) : []; // last 24h, 1 sample per min + if (history.length) { + const latest = history[history.length - 1]; + network.rxKBs = Math.round((latest.rx_bytes || 0) / 1024); + network.txKBs = Math.round((latest.tx_bytes || 0) / 1024); + // For sparkline: use last 60 samples, convert to KB/s + const recent = history.slice(-60); + network.rx = recent.map(h => (h.rx_bytes || 0) / 1024); + network.tx = recent.map(h => (h.tx_bytes || 0) / 1024); + network.history = recent.map(h => (h.tx_bytes + h.rx_bytes) / 1024); + } + } catch (_) {} + + // Last scan info + let lastScan = null; + if (latest) { + lastScan = { + timestamp: latest.timestamp, + filesScanned: latest.files_scanned, + threatsFound: latest.threats_found + }; + } + return { score: result.data.score, - detail: disk?.reason || 'Protection and resource summary ready.' + detail: disk?.reason || 'Protection and resource summary ready.', + rtp, + firewall, + network, + lastScan }; } -module.exports = { getTrayHealthSummary }; +module.exports = { getTrayHealthSummary }; \ No newline at end of file diff --git a/src/main/main.js b/src/main/main.js index a8c7aa2..bb52dc4 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -578,7 +578,27 @@ function buildAppMenu() { app.setAppUserModelId('com.soterios.app'); +const gotTheLock = app.requestSingleInstanceLock(); +if (!gotTheLock) { + app.quit(); + process.exit(0); +} + +app.on('second-instance', (_event, commandLine) => { + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); + const url = commandLine.find(arg => arg.startsWith('soterios://')); + if (url) mainWindow.webContents.send('protocol-url', url); + } +}); + app.whenReady().then(async () => { + // Register custom protocol for browser extension communication + if (process.platform === 'win32') { + app.setAsDefaultProtocolClient('soterios'); + } + const dbPath = path.join(app.getPath('userData'), 'soterios.db'); // File logging is opt-in via SOTERIOS_LOG_FILE (path or "1" for the default log file). const logConfig = { level: process.env.SOTERIOS_LOG_LEVEL || 'info' }; diff --git a/src/ui/js/pages/settings.js b/src/ui/js/pages/settings.js index b7499c9..9f5c7ad 100644 --- a/src/ui/js/pages/settings.js +++ b/src/ui/js/pages/settings.js @@ -119,6 +119,14 @@ window.Pages.settings = { + +
+
+
${escapeHtml(t('settings.browserExtension.label'))}
+
${escapeHtml(t('settings.browserExtension.desc'))}
+
+ +
@@ -384,6 +392,33 @@ window.Pages.settings = { container.querySelector('#externalLookupsToggle').addEventListener('change', (event) => saveFeature('externalLookups', event.target.checked, event.target)); container.querySelector('#geoLookupToggle').addEventListener('change', (event) => saveFeature('geoLookup', event.target.checked, event.target)); container.querySelector('#networkPerimeterMapToggle').addEventListener('change', (event) => saveFeature('networkPerimeterMap', event.target.checked, event.target)); + container.querySelector('#browserExtensionToggle').addEventListener('change', async (event) => { + const checked = event.target.checked; + const statusEl = container.querySelector('#featureToggleStatus'); + statusEl.textContent = ''; + event.target.disabled = true; + try { + await Api.updateSettings({ features: { browserExtension: checked } }); + if (checked) { + statusEl.textContent = t('settings.browserExtension.installing'); + const result = await window.api.invoke('browserExtension:installNativeHost'); + if (result.ok) { + statusEl.textContent = t('settings.browserExtension.installed'); + } else { + event.target.checked = false; + await Api.updateSettings({ features: { browserExtension: false } }); + statusEl.textContent = result.error || t('settings.browserExtension.installFailed'); + } + } else { + statusEl.textContent = t('settings.featureSaved'); + } + } catch (err) { + event.target.checked = !checked; + statusEl.textContent = err.message || String(err); + } finally { + event.target.disabled = false; + } + }); container.querySelector('#notificationsToggle').addEventListener('change', async (event) => { const checked = event.target.checked; const statusEl = container.querySelector('#notificationStatus'); diff --git a/src/ui/pages/trayDashboard.html b/src/ui/pages/trayDashboard.html index e4b198c..1a6e3e0 100644 --- a/src/ui/pages/trayDashboard.html +++ b/src/ui/pages/trayDashboard.html @@ -11,6 +11,13 @@ --text: #f2f5f8; --muted: #aab4bf; --accent: #58a6ff; + --accent-bg: rgba(88,166,255,0.15); + --danger: #f85149; + --danger-bg: rgba(248,81,73,0.15); + --ok: #3fb950; + --ok-bg: rgba(63,185,80,0.15); + --spark-rx: #58a6ff; + --spark-tx: #f85149; } html, body { margin: 0; @@ -27,15 +34,46 @@ box-shadow: 0 12px 32px rgba(0,0,0,0.35); color: var(--text); } - .title { font-size: 14px; font-weight: 700; margin-bottom: 4px; } + .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; } + .title { font-size: 14px; font-weight: 700; } + .status-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + } + .status-badge.active { background: var(--ok-bg); color: #3fb950; } + .status-badge.inactive { background: var(--danger-bg); color: #f85149; } + .status-dot { + width: 6px; height: 6px; border-radius: 50%; + background: currentColor; + } + .score-row { display: flex; align-items: baseline; gap: 12px; margin: 8px 0 4px; } .score { font-size: 42px; font-weight: 700; color: var(--accent); line-height: 1; + } + .score-detail { font-size: 12px; color: var(--muted); line-height: 1.5; } + .rtp-row { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-top: 1px solid var(--border); } + .rtp-label { font-size: 12px; color: var(--muted); flex: 1; } + .sparkline { + height: 40px; margin: 8px 0; + position: relative; + } + .sparkline canvas { width: 100%; height: 100%; } + .sparkline-labels { + display: flex; + justify-content: space-between; + font-size: 9px; + color: var(--muted); + margin-top: 2px; } - .meta { font-size: 12px; color: var(--muted); line-height: 1.5; } .actions { display: flex; gap: 8px; margin-top: 14px; } button { flex: 1; @@ -46,40 +84,178 @@ padding: 8px 10px; font-size: 12px; cursor: pointer; + transition: background 0.15s, border-color 0.15s; } + button:hover { background: rgba(255,255,255,0.08); } button.primary { background: var(--accent); border-color: transparent; color: #0b0e14; font-weight: 600; } + button.primary:hover { background: #4a9eff; } + button.secondary:hover { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); }
-
System Health
-
--
-
Loading summary...
+
+
System Health
+ + + RTP + +
+ +
+
--
+
+
Loading summary...
+ +
+ Network +
+ +
+ 0 KB/s + 0 KB/s +
+
+
+
- - + + +
+ - + \ No newline at end of file diff --git a/src/ui/pages/trayDashboard.js b/src/ui/pages/trayDashboard.js new file mode 100644 index 0000000..d76ca4e --- /dev/null +++ b/src/ui/pages/trayDashboard.js @@ -0,0 +1,166 @@ +window.api.on('tray:summary', (summary) => { + if (!summary) return; + + const scoreEl = document.getElementById('scoreEl'); + const detailEl = document.getElementById('detailEl'); + const rtpDot = document.getElementById('rtpDot'); + const rtpLabel = document.getElementById('rtpLabel'); + const rtpStatus = document.getElementById('rtpStatus'); + const fwDot = document.getElementById('fwDot'); + const fwStatus = document.getElementById('fwStatus'); + const rxRate = document.getElementById('rxRate'); + const txRate = document.getElementById('txRate'); + const lastScan = document.getElementById('lastScan'); + + // Score + if (summary.score != null) { + scoreEl.textContent = summary.score; + scoreEl.className = 'score ' + (summary.score >= 80 ? 'pass' : summary.score >= 50 ? 'warn' : 'fail'); + } else { + scoreEl.textContent = '—'; + scoreEl.className = 'score'; + } + detailEl.textContent = summary.detail || 'Health summary unavailable.'; + + // RTP + if (summary.rtp) { + rtpDot.className = 'status-dot ' + (summary.rtp.enabled ? 'active' : 'inactive'); + rtpLabel.textContent = summary.rtp.enabled ? 'RTP Active' : 'RTP Disabled'; + rtpStatus.textContent = summary.rtp.enabled ? 'Monitoring file system' : 'Click to enable'; + } else { + rtpDot.className = 'status-dot unknown'; + rtpLabel.textContent = 'RTP Unknown'; + rtpStatus.textContent = '—'; + } + + // Firewall + if (summary.firewall) { + fwDot.className = 'status-dot ' + (summary.firewall.active ? 'active' : 'inactive'); + fwStatus.textContent = summary.firewall.active ? 'Active' : 'Disabled'; + } else { + fwDot.className = 'status-dot unknown'; + fwStatus.textContent = '—'; + } + + // Network rates + if (summary.network) { + document.getElementById('rxRate').textContent = summary.network.rxKBs || 0; + document.getElementById('txRate').textContent = summary.network.txKBs || 0; + drawSparkline(summary.network.history || []); + } + + // Last scan + if (summary.lastScan) { + const scan = summary.lastScan; + const when = scan.timestamp ? new Date(scan.timestamp).toLocaleString() : 'Unknown'; + document.getElementById('lastScan').textContent = + `${when} · ${scan.filesScanned || 0} files · ${scan.threatsFound || 0} threats`; + } +}); + +async function loadSummary() { + try { + const summary = await window.api.invoke('tray:getSummary'); + if (summary) { + const scoreEl = document.getElementById('scoreEl'); + const detailEl = document.getElementById('detailEl'); + if (summary.score != null) { + scoreEl.textContent = summary.score; + scoreEl.className = 'score ' + (summary.score >= 80 ? 'pass' : summary.score >= 50 ? 'warn' : 'fail'); + } + detailEl.textContent = summary.detail || 'Health summary unavailable.'; + + // Update RTP, firewall, network, last scan from summary + if (summary.rtp) { + const rtpDot = document.getElementById('rtpDot'); + const rtpLabel = document.getElementById('rtpLabel'); + const rtpStatus = document.getElementById('rtpStatus'); + rtpDot.className = 'status-dot ' + (summary.rtp.enabled ? 'active' : 'inactive'); + rtpLabel.textContent = summary.rtp.enabled ? 'RTP Active' : 'RTP Disabled'; + rtpStatus.textContent = summary.rtp.enabled ? 'Monitoring file system' : 'Click to enable'; + } + if (summary.firewall) { + const fwDot = document.getElementById('fwDot'); + const fwStatus = document.getElementById('fwStatus'); + fwDot.className = 'status-dot ' + (summary.firewall.active ? 'active' : 'inactive'); + fwStatus.textContent = summary.firewall.active ? 'Active' : 'Disabled'; + } + if (summary.network) { + document.getElementById('rxRate').textContent = summary.network.rxKBs || 0; + document.getElementById('txRate').textContent = summary.network.txKBs || 0; + drawSparkline(summary.network.history || []); + } + if (summary.lastScan) { + const scan = summary.lastScan; + const when = scan.timestamp ? new Date(scan.timestamp).toLocaleString() : 'Unknown'; + document.getElementById('lastScan').textContent = + `${when} · ${scan.filesScanned || 0} files · ${scan.threatsFound || 0} threats`; + } + } + } catch (e) { + console.error('Failed to load tray summary:', e); + document.getElementById('detailEl').textContent = 'Unable to load health summary.'; + } +} + +function drawSparkline(history) { + const canvas = document.getElementById('sparkCanvas'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + const dpr = window.devicePixelRatio || 1; + const rect = canvas.getBoundingClientRect(); + canvas.width = rect.width * dpr; + canvas.height = rect.height * dpr; + ctx.scale(dpr, dpr); + ctx.clearRect(0, 0, rect.width, rect.height); + + if (!history.length) return; + + const maxVal = Math.max(...history, 1); + const minVal = Math.min(...history); + const range = maxVal - minVal || 1; + + ctx.strokeStyle = '#58a6ff'; + ctx.lineWidth = 2; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.beginPath(); + + history.forEach((val, i) => { + const x = (i / (history.length - 1 || 1)) * rect.width; + const y = rect.height - ((val - minVal) / range) * rect.height * 0.85 - rect.height * 0.075; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + ctx.stroke(); + + // Fill gradient + const grad = ctx.createLinearGradient(0, 0, 0, rect.height); + grad.addColorStop(0, 'rgba(88,166,255,0.15)'); + grad.addColorStop(1, 'rgba(88,166,255,0)'); + ctx.fillStyle = grad; + ctx.lineTo(rect.width, rect.height); + ctx.lineTo(0, rect.height); + ctx.closePath(); + ctx.fill(); +} + +document.getElementById('btnQuickScan').addEventListener('click', async () => { + const btn = document.getElementById('btnQuickScan'); + btn.disabled = true; + btn.textContent = 'Starting...'; + try { + await window.api.invoke('scan:quick'); + btn.textContent = 'Quick Scan'; + } catch (e) { + btn.textContent = 'Failed'; + setTimeout(() => { btn.disabled = false; btn.textContent = 'Quick Scan'; }, 2000); + } +}); + +document.getElementById('btnOpen').addEventListener('click', () => { + window.api.invoke('tray:openMain'); +}); + +loadSummary(); +setInterval(loadSummary, 15000); // Refresh every 15s \ No newline at end of file diff --git a/tools/build-icons.js b/tools/build-icons.js new file mode 100644 index 0000000..35cea89 --- /dev/null +++ b/tools/build-icons.js @@ -0,0 +1,22 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const sizes = [16, 32, 48, 128]; +const svgPath = path.join(__dirname, '../browser-extension/icons/icon.svg'); +const iconsDir = path.join(__dirname, '../browser-extension/icons'); + +if (!fs.existsSync(svgPath)) { + console.error('icon.svg not found'); + process.exit(1); +} + +for (const size of sizes) { + const outPath = path.join(iconsDir, `icon${size}.png`); + try { + execSync(`npx -y svgexport "${svgPath}" "${outPath}" ${size}:${size}`, { stdio: 'inherit' }); + console.log(`Generated ${outPath}`); + } catch (e) { + console.error(`Failed to generate ${size}px icon:`, e.message); + } +} \ No newline at end of file diff --git a/tools/install-native-host.js b/tools/install-native-host.js new file mode 100644 index 0000000..84a71e3 --- /dev/null +++ b/tools/install-native-host.js @@ -0,0 +1,64 @@ +#!/usr/bin/env node +/** + * Install Soterios Native Messaging Host + * Run as Administrator on Windows + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const EXTENSION_ID = process.env.EXTENSION_ID || 'YOUR_EXTENSION_ID_HERE'; +const IS_WIN = process.platform === 'win32'; + +function main() { + const extDir = path.resolve(__dirname, '..', 'browser-extension'); + const manifestPath = path.join(extDir, 'native-host-manifest.json'); + const batPath = path.join(extDir, 'native-host.bat'); + const jsPath = path.join(extDir, 'native-host.js'); + + if (!fs.existsSync(manifestPath) || !fs.existsSync(batPath) || !fs.existsSync(jsPath)) { + console.error('Extension files not found. Run from project root.'); + process.exit(1); + } + + let manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + manifest.allowed_origins = [manifest.allowed_origins[0].replace('', EXTENSION_ID)]; + + // Write updated manifest back to disk so registry points to correct file + fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); + + if (IS_WIN) { + const regPath = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${manifest.name}`; + const regCmd = `reg add "${regPath}" /ve /t REG_SZ /d "${manifestPath.replace(/\\/g, '\\\\')}" /f`; + try { + execSync(regCmd, { stdio: 'inherit' }); + console.log('Registered native host for Chrome (Current User)'); + } catch (e) { + console.error('Failed to register (run as Administrator):', e.message); + process.exit(1); + } + + const regPathEdge = `HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\${manifest.name}`; + const regCmdEdge = `reg add "${regPathEdge}" /ve /t REG_SZ /d "${manifestPath.replace(/\\/g, '\\\\')}" /f`; + try { + execSync(regCmdEdge, { stdio: 'inherit' }); + console.log('Registered native host for Edge (Current User)'); + } catch (e) { + console.warn('Edge registration failed:', e.message); + } + } else { + const dir = process.platform === 'darwin' + ? path.join(process.env.HOME, 'Library', 'Application Support', 'Google', 'Chrome', 'NativeMessagingHosts') + : path.join(process.env.HOME, '.config', 'google-chrome', 'NativeMessagingHosts'); + + fs.mkdirSync(dir, { recursive: true }); + const target = path.join(dir, `${manifest.name}.json`); + fs.writeFileSync(target, JSON.stringify(manifest, null, 2)); + console.log('Installed manifest to:', target); + } + + console.log('\nDone! Reload the extension in chrome://extensions'); +} + +main(); \ No newline at end of file