Verstak (GUI): - SettingsBrowserBridge.svelte — новая секция в Settings: toggle вкл/выкл сервера, поле порта (default 9786), статус (Запущен/Остановлен), кнопки Сохранить/Перезапустить - SettingsWindow + SettingsSidebar — подключена секция browserBridge - BridgeConfig: добавлено поле Enabled (default true) - RestartBridge() — новый биндинг для перезапуска сервера - initVault: проверяет bc.Enabled перед запуском bridge Extension (Chrome + Firefox): - popup: панель настроек с полем порта (default 9786) - кнопка «Проверить» — fetch /api/ping с таймаутом 3с - кнопка «Сохранить» — сохраняет port в chrome.storage.local - статус соединения: ✓ Сервер отвечает / ✗ Недоступен / ✗ Таймаут - оба расширения работают только с 127.0.0.1
255 lines
6.3 KiB
Svelte
255 lines
6.3 KiB
Svelte
<script>
|
|
import { t } from './i18n'
|
|
|
|
export let appConfig = null
|
|
|
|
let bridgeEnabled = false
|
|
let bridgePort = 9786
|
|
let bridgeRunning = false
|
|
let bridgeActualPort = 0
|
|
let saving = false
|
|
let statusMsg = ''
|
|
let statusKind = '' // 'ok' | 'error' | ''
|
|
|
|
// Load current bridge state from config
|
|
function loadFromConfig() {
|
|
if (appConfig && appConfig.vault && appConfig.vault.bridge) {
|
|
const bc = appConfig.vault.bridge
|
|
bridgeEnabled = bc.enabled !== false
|
|
bridgePort = bc.port || 9786
|
|
}
|
|
}
|
|
|
|
// Also fetch live bridge status from backend
|
|
async function fetchBridgeStatus() {
|
|
try {
|
|
if (window['go'] && window['go']['main'] && window['go']['main']['App']) {
|
|
const info = await window['go']['main']['App']['BridgeInfo']()
|
|
bridgeRunning = info.running || false
|
|
bridgeActualPort = info.port || 0
|
|
}
|
|
} catch (e) {
|
|
bridgeRunning = false
|
|
bridgeActualPort = 0
|
|
}
|
|
}
|
|
|
|
loadFromConfig()
|
|
fetchBridgeStatus()
|
|
|
|
async function save() {
|
|
saving = true
|
|
statusMsg = ''
|
|
statusKind = ''
|
|
try {
|
|
// Update config
|
|
if (!appConfig) appConfig = {}
|
|
if (!appConfig.vault) appConfig.vault = {}
|
|
if (!appConfig.vault.bridge) appConfig.vault.bridge = {}
|
|
appConfig.vault.bridge.enabled = bridgeEnabled
|
|
appConfig.vault.bridge.port = bridgePort
|
|
|
|
if (window['go'] && window['go']['main'] && window['go']['main']['App']) {
|
|
await window['go']['main']['App']['SaveAppConfig'](appConfig)
|
|
}
|
|
statusMsg = 'saved'
|
|
statusKind = 'ok'
|
|
// Refresh live status
|
|
await fetchBridgeStatus()
|
|
} catch (e) {
|
|
statusMsg = String(e)
|
|
statusKind = 'error'
|
|
}
|
|
saving = false
|
|
}
|
|
|
|
async function restartBridge() {
|
|
saving = true
|
|
statusMsg = ''
|
|
statusKind = ''
|
|
try {
|
|
if (window['go'] && window['go']['main'] && window['go']['main']['App']) {
|
|
await window['go']['main']['App']['RestartBridge']()
|
|
}
|
|
statusMsg = 'bridge restarted'
|
|
statusKind = 'ok'
|
|
await fetchBridgeStatus()
|
|
} catch (e) {
|
|
statusMsg = String(e)
|
|
statusKind = 'error'
|
|
}
|
|
saving = false
|
|
}
|
|
</script>
|
|
|
|
<div class="settings-section">
|
|
<h2>Browser Bridge</h2>
|
|
<p class="section-desc">
|
|
Локальный HTTP-сервер для приёма событий от браузерного расширения Verstak Bridge.
|
|
Работает только на 127.0.0.1.
|
|
</p>
|
|
|
|
{#if statusMsg}
|
|
<div class="status-msg" class:ok={statusKind === 'ok'} class:error={statusKind === 'error'}>
|
|
{statusMsg}
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="settings-card">
|
|
<div class="form-group">
|
|
<label class="toggle-label">
|
|
<input type="checkbox" bind:checked={bridgeEnabled} />
|
|
<span class="toggle-text">Включить Browser Bridge</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>
|
|
<span class="label-text">Порт</span>
|
|
<input type="number" bind:value={bridgePort} min="1024" max="65535" disabled={!bridgeEnabled} />
|
|
</label>
|
|
<p class="field-hint">По умолчанию: 9786. Требуется перезапуск сервера при изменении.</p>
|
|
</div>
|
|
|
|
<div class="bridge-status">
|
|
<div class="info-row">
|
|
<span class="info-label">Статус</span>
|
|
<span class="info-value" class:status-ok={bridgeRunning} class:status-off={!bridgeRunning}>
|
|
{bridgeRunning ? 'Запущен' : 'Остановлен'}
|
|
</span>
|
|
</div>
|
|
{#if bridgeRunning && bridgeActualPort}
|
|
<div class="info-row">
|
|
<span class="info-label">Активный порт</span>
|
|
<span class="info-value mono">127.0.0.1:{bridgeActualPort}</span>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button class="btn btn-primary" on:click={save} disabled={saving}>
|
|
Сохранить
|
|
</button>
|
|
{#if bridgeEnabled}
|
|
<button class="btn" on:click={restartBridge} disabled={saving}>
|
|
Перезапустить сервер
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.settings-section {
|
|
padding: 1.5rem;
|
|
max-width: 600px;
|
|
}
|
|
.settings-section h2 {
|
|
margin: 0 0 0.25rem 0;
|
|
font-size: 1.2rem;
|
|
color: var(--text, #e0e0e0);
|
|
}
|
|
.section-desc {
|
|
color: var(--text-dim, #888);
|
|
font-size: 0.85rem;
|
|
margin-bottom: 1.25rem;
|
|
line-height: 1.4;
|
|
}
|
|
.settings-card {
|
|
background: var(--surface-alt, #1e1e30);
|
|
border: 1px solid var(--border, #2a2a3e);
|
|
border-radius: 8px;
|
|
padding: 1rem 1.25rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
.form-group {
|
|
margin-bottom: 1rem;
|
|
}
|
|
.form-group:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
.label-text {
|
|
display: block;
|
|
color: var(--text-dim, #888);
|
|
font-size: 0.85rem;
|
|
margin-bottom: 0.35rem;
|
|
}
|
|
input[type="number"] {
|
|
width: 120px;
|
|
padding: 0.4rem 0.6rem;
|
|
background: var(--bg, #13131f);
|
|
border: 1px solid var(--border, #2a2a3e);
|
|
border-radius: 6px;
|
|
color: var(--text, #e0e0e0);
|
|
font-size: 0.9rem;
|
|
}
|
|
input[type="number"]:disabled {
|
|
opacity: 0.5;
|
|
}
|
|
.field-hint {
|
|
color: var(--text-dim, #666);
|
|
font-size: 0.75rem;
|
|
margin-top: 0.35rem;
|
|
}
|
|
.toggle-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
cursor: pointer;
|
|
}
|
|
.toggle-text {
|
|
color: var(--text, #e0e0e0);
|
|
font-size: 0.9rem;
|
|
}
|
|
.bridge-status {
|
|
margin-top: 0.75rem;
|
|
padding-top: 0.75rem;
|
|
border-top: 1px solid var(--border, #2a2a3e);
|
|
}
|
|
.info-row {
|
|
display: flex;
|
|
padding: 0.3rem 0;
|
|
font-size: 0.85rem;
|
|
}
|
|
.info-label {
|
|
width: 140px;
|
|
min-width: 140px;
|
|
color: var(--text-dim, #888);
|
|
}
|
|
.info-value {
|
|
color: var(--text, #e0e0e0);
|
|
}
|
|
.info-value.mono {
|
|
font-family: monospace;
|
|
font-size: 0.85rem;
|
|
}
|
|
.status-ok {
|
|
color: #34d399;
|
|
font-weight: 600;
|
|
}
|
|
.status-off {
|
|
color: #888;
|
|
}
|
|
.actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
.status-msg {
|
|
padding: 0.5rem 0.75rem;
|
|
margin-bottom: 0.75rem;
|
|
border-radius: 6px;
|
|
font-size: 0.85rem;
|
|
}
|
|
.status-msg.ok {
|
|
background: rgba(52, 211, 153, 0.1);
|
|
border: 1px solid rgba(52, 211, 153, 0.3);
|
|
color: #34d399;
|
|
}
|
|
.status-msg.error {
|
|
background: rgba(255, 107, 107, 0.1);
|
|
border: 1px solid rgba(255, 107, 107, 0.3);
|
|
color: #ff6b6b;
|
|
}
|
|
</style>
|