mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-11-05 13:06:21 +00:00
Keep quick settings focused on the model preset, Host/Container choice, native page zoom, and installed Chrome extensions. Show a dimmed host configuration link only in Host mode, and match the Browser and Zoom labels to the preset label. Move extension creation, installation, and review into Browser config. Open Chromium management pages for browsing data, history, passwords, autofill, extensions, and downloads through the existing Browser surface. Honor native download folder and save-dialog preferences, and add annotation screenshots as an opt-in setting that defaults off. Update the Browser contracts and regression coverage. Validation: 201 browser/tool tests and 29 host-connector tests passed, with the host suite isolated from existing module stubs. Live checks on localhost:32083 covered settings navigation, runtime selection, zoom, annotation attachments, download preferences, matching typography, and narrow layouts.
1032 lines
41 KiB
HTML
1032 lines
41 KiB
HTML
<html>
|
|
<head>
|
|
<title>Browser Settings</title>
|
|
<script type="module">
|
|
import { store } from "/plugins/_browser/webui/browser-config-store.js";
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
<div x-data>
|
|
<template x-if="$store.browserConfig && config">
|
|
<div
|
|
class="browser-config-sections"
|
|
x-init="$store.browserConfig.init(config)"
|
|
x-effect="$store.browserConfig.bindConfig(config)"
|
|
x-destroy="$store.browserConfig.cleanup()"
|
|
>
|
|
<div class="browser-config-card">
|
|
<div class="section-title">Browser Runtime</div>
|
|
<div class="section-description">
|
|
Choose the internal Docker browser or a host browser connected through Launcher or A0 CLI.
|
|
</div>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Browser location</span>
|
|
<select x-model="$store.browserConfig.config.runtime_backend">
|
|
<option value="container">Internal Docker browser</option>
|
|
<option value="host_required">Host browser on connected computer</option>
|
|
</select>
|
|
<span
|
|
class="browser-config-field-help"
|
|
x-show="$store.browserConfig.config.runtime_backend !== 'host_required'"
|
|
>
|
|
Runs inside Agent Zero's Docker environment. No host connection or browser setup is needed.
|
|
</span>
|
|
<span
|
|
class="browser-config-field-help"
|
|
x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
|
|
>
|
|
Uses Safari, Chrome, Edge, Brave, Opera, Vivaldi, or Chromium on a computer connected through Launcher or A0 CLI. The browser opens when the agent first needs it.
|
|
</span>
|
|
</label>
|
|
|
|
<div
|
|
class="browser-config-note browser-config-success"
|
|
x-show="$store.browserConfig.config.runtime_backend !== 'host_required'"
|
|
>
|
|
<x-icon name="deployed_code"></x-icon>
|
|
<span>Internal Docker browser selected. Host-browser connection settings are ignored for Browser tool runs.</span>
|
|
</div>
|
|
|
|
<details
|
|
class="browser-config-proxy"
|
|
x-show="$store.browserConfig.config.runtime_backend === 'container'"
|
|
>
|
|
<summary>Proxy server</summary>
|
|
<div class="browser-config-proxy-body">
|
|
<label class="browser-config-field">
|
|
<input
|
|
type="text"
|
|
x-model="$store.browserConfig.config.proxy_server"
|
|
placeholder="http://proxy.example:3128 or socks5://proxy.example:1080"
|
|
aria-label="Proxy server"
|
|
autocomplete="off"
|
|
/>
|
|
<span class="browser-config-field-help">Optional. Routes the internal browser through this HTTP or SOCKS proxy.</span>
|
|
</label>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Bypass proxy for</span>
|
|
<input
|
|
type="text"
|
|
x-model="$store.browserConfig.config.proxy_bypass"
|
|
placeholder="localhost, .example.com"
|
|
autocomplete="off"
|
|
/>
|
|
<span class="browser-config-field-help">Optional comma-separated domains that should connect directly.</span>
|
|
</label>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Proxy username</span>
|
|
<input
|
|
type="text"
|
|
x-model="$store.browserConfig.config.proxy_username"
|
|
autocomplete="off"
|
|
/>
|
|
</label>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Proxy password</span>
|
|
<input
|
|
type="password"
|
|
x-model="$store.browserConfig.config.proxy_password"
|
|
autocomplete="off"
|
|
/>
|
|
<span class="browser-config-field-help">Saving proxy changes restarts active internal Browser sessions.</span>
|
|
</label>
|
|
</div>
|
|
</details>
|
|
|
|
<label
|
|
class="browser-config-field"
|
|
x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
|
|
>
|
|
<span class="browser-config-field-label">Host browser profile</span>
|
|
<select x-model="$store.browserConfig.config.host_browser_profile_mode">
|
|
<option value="existing">Existing browser profile</option>
|
|
<option value="agent">Clean Agent profile</option>
|
|
</select>
|
|
<span
|
|
class="browser-config-field-help"
|
|
x-show="$store.browserConfig.config.host_browser_profile_mode === 'agent'"
|
|
>
|
|
Uses a separate A0-controlled local profile on this computer.
|
|
</span>
|
|
</label>
|
|
|
|
<label
|
|
class="browser-config-field"
|
|
x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent'"
|
|
>
|
|
<span class="browser-config-field-label">Host browser</span>
|
|
<select
|
|
:value="$store.browserConfig.hostBrowserSelectValue()"
|
|
@focus="$store.browserConfig.loadHostBrowserStatus()"
|
|
@click="$store.browserConfig.loadHostBrowserStatus()"
|
|
@change="$store.browserConfig.setHostBrowserSelection($event.target.value)"
|
|
>
|
|
<template x-for="option in $store.browserConfig.hostBrowserOptions()" :key="option.value">
|
|
<option :value="option.value" x-text="option.label"></option>
|
|
</template>
|
|
</select>
|
|
<span class="browser-config-field-help">Choose an available host browser, or leave Automatic to use the first available one.</span>
|
|
</label>
|
|
|
|
<label
|
|
class="browser-config-field"
|
|
x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent' && $store.browserConfig.showCustomHostBrowserEndpoint()"
|
|
>
|
|
<span class="browser-config-field-label">Custom endpoint</span>
|
|
<input
|
|
type="text"
|
|
:value="$store.browserConfig.hostBrowserCustomEndpoint"
|
|
@focus="$store.browserConfig.loadHostBrowserStatus()"
|
|
@input="$store.browserConfig.setCustomHostBrowserEndpoint($event.target.value)"
|
|
placeholder="ws://127.0.0.1:9222/devtools/browser/..."
|
|
autocomplete="off"
|
|
/>
|
|
<span class="browser-config-field-help" x-text="$store.browserConfig.customHostBrowserEndpointDiagnostic()"></span>
|
|
</label>
|
|
|
|
<div
|
|
class="browser-config-guide"
|
|
x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent'"
|
|
>
|
|
<div class="browser-config-guide-heading">
|
|
<x-icon name="tune"></x-icon>
|
|
<span>Browser setup</span>
|
|
</div>
|
|
|
|
<div class="browser-config-guide-grid">
|
|
<div class="browser-config-guide-item" aria-label="Chromium browser setup steps">
|
|
<div class="browser-config-guide-title">
|
|
<x-icon name="developer_mode"></x-icon>
|
|
<span>Chromium family</span>
|
|
</div>
|
|
<div class="browser-config-setup-buttons" role="group" aria-label="Open Chromium browser setup page">
|
|
<button
|
|
type="button"
|
|
class="browser-config-setup-button"
|
|
x-show="$store.browserConfig.hostBrowserSetupAvailable('chrome')"
|
|
title="Open chrome://inspect/#remote-debugging in Chrome"
|
|
:disabled="!!$store.browserConfig.hostBrowserSetupOpening"
|
|
@click="$store.browserConfig.openHostBrowserSetup('chrome')"
|
|
>
|
|
<span>Chrome</span>
|
|
<x-icon name="open_in_new"></x-icon>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="browser-config-setup-button"
|
|
x-show="$store.browserConfig.hostBrowserSetupAvailable('opera')"
|
|
title="Open opera://inspect/#remote-debugging in Opera"
|
|
:disabled="!!$store.browserConfig.hostBrowserSetupOpening"
|
|
@click="$store.browserConfig.openHostBrowserSetup('opera')"
|
|
>
|
|
<span>Opera</span>
|
|
<x-icon name="open_in_new"></x-icon>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="browser-config-setup-button"
|
|
x-show="$store.browserConfig.hostBrowserSetupAvailable('edge')"
|
|
title="Open edge://inspect/#remote-debugging in Edge"
|
|
:disabled="!!$store.browserConfig.hostBrowserSetupOpening"
|
|
@click="$store.browserConfig.openHostBrowserSetup('edge')"
|
|
>
|
|
<span>Edge</span>
|
|
<x-icon name="open_in_new"></x-icon>
|
|
</button>
|
|
</div>
|
|
<p>Enable <strong>Allow remote debugging for this browser instance</strong>; reconnect the host connection if needed.</p>
|
|
<p class="browser-config-guide-support">Brave, Vivaldi, and Chromium are supported through their own inspect pages.</p>
|
|
</div>
|
|
|
|
<div class="browser-config-guide-item">
|
|
<div class="browser-config-guide-title">
|
|
<x-icon name="web_asset"></x-icon>
|
|
<span>Safari <small>macOS</small></span>
|
|
</div>
|
|
<ol aria-label="Safari setup steps">
|
|
<li>Open <strong>Safari Settings > Advanced</strong> and enable <strong>Show features for web developers</strong>.</li>
|
|
<li>Open <strong>Developer</strong> and enable <strong>Allow remote automation</strong>.</li>
|
|
</ol>
|
|
<p>Safari uses a dedicated automation window and needs no debug endpoint.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<details class="browser-config-guide-details">
|
|
<summary>Manual debug endpoint</summary>
|
|
<p>
|
|
Launch the browser with <code>--remote-debugging-port=9222</code> and
|
|
<code>--user-data-dir=<profile-dir></code>, then use <code>localhost:9222</code> or the full
|
|
<code>ws://.../devtools/browser/...</code> endpoint in Custom endpoint or
|
|
<code>A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS</code>.
|
|
</p>
|
|
</details>
|
|
</div>
|
|
|
|
<div
|
|
class="browser-config-warning"
|
|
x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent'"
|
|
>
|
|
<x-icon name="warning"></x-icon>
|
|
<span>
|
|
Existing profile lets the agent interact with the browser instance you authorize,
|
|
including signed-in sites, cookies, tabs, downloads, and page content.
|
|
</span>
|
|
</div>
|
|
|
|
<label
|
|
class="browser-config-field"
|
|
x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
|
|
>
|
|
<span class="browser-config-field-label">Page content access</span>
|
|
<select x-model="$store.browserConfig.config.host_browser_privacy_policy">
|
|
<option value="enforce_local">Local models only</option>
|
|
<option value="warn">Warn when using cloud</option>
|
|
<option value="allow">Allow</option>
|
|
</select>
|
|
<span class="browser-config-field-help">Controls page text and screenshots from the host browser.</span>
|
|
</label>
|
|
|
|
<div class="browser-config-note">
|
|
<x-icon :class="{ spinning: $store.browserConfig.hostBrowserStatusLoading }" :name="$store.browserConfig.hostBrowserStatusLoading ? 'progress_activity' : 'captive_portal'"></x-icon>
|
|
<span x-text="$store.browserConfig.browserRuntimeStatusLabel()"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="browser-config-card">
|
|
<div class="section-title">Browsing</div>
|
|
<div class="section-description">
|
|
Set how new Browser sessions start and how an already-open Browser surface follows agent activity.
|
|
</div>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Starting page</span>
|
|
<input
|
|
type="text"
|
|
x-model="$store.browserConfig.config.default_homepage"
|
|
placeholder="about:blank or https://example.com"
|
|
autocomplete="off"
|
|
/>
|
|
</label>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Browser tabs</span>
|
|
<select
|
|
x-model="$store.browserConfig.config.browser_tab_scope"
|
|
@change="$store.browserConfig.setBrowserTabScope($event.target.value)"
|
|
>
|
|
<option value="per_context">Separate per chat</option>
|
|
<option value="shared">Shared across chats</option>
|
|
</select>
|
|
<span
|
|
class="browser-config-field-help"
|
|
x-show="$store.browserConfig.config.browser_tab_scope !== 'shared'"
|
|
>
|
|
Each chat shows only its own tabs. Browser sign-ins are shared across chats.
|
|
</span>
|
|
<span
|
|
class="browser-config-field-help"
|
|
x-show="$store.browserConfig.config.browser_tab_scope === 'shared'"
|
|
>
|
|
The tab strip shows tabs from every active chat. Browser sign-ins are shared across chats.
|
|
</span>
|
|
</label>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Maximum tabs per chat</span>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
max="50"
|
|
step="1"
|
|
x-model.number="$store.browserConfig.config.max_open_tabs"
|
|
@change="$store.browserConfig.normalizeMaxOpenTabs()"
|
|
/>
|
|
<span class="browser-config-field-help">
|
|
New Browser tabs stop opening in a chat after this limit.
|
|
</span>
|
|
</label>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Evaluate timeout (seconds)</span>
|
|
<input
|
|
type="number"
|
|
min="0.1"
|
|
max="60"
|
|
step="0.1"
|
|
x-model.number="$store.browserConfig.config.evaluate_timeout_seconds"
|
|
/>
|
|
<span class="browser-config-field-help">
|
|
JavaScript deadline, from 0.1 to 60 seconds. Recovery may reload the tab and lose unsaved page edits.
|
|
</span>
|
|
</label>
|
|
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Keyboard layout</span>
|
|
<input
|
|
type="text"
|
|
x-model="$store.browserConfig.config.keyboard_layout"
|
|
placeholder="de, fr, us, ..."
|
|
autocomplete="off"
|
|
/>
|
|
<span class="browser-config-field-help">
|
|
XKB layout of the interactive Browser window, e.g. de for a German keyboard. Match your physical keyboard so characters like @, umlauts, and brackets land on the printed keys. Empty keeps US.
|
|
<a href="https://man.archlinux.org/man/xkeyboard-config.7#LAYOUTS" target="_blank" rel="noreferrer">Full layout list</a>. Changing it restarts the internal browser on save.
|
|
</span>
|
|
</label>
|
|
|
|
<label
|
|
class="browser-config-field"
|
|
x-show="$store.browserConfig.config.keyboard_layout"
|
|
>
|
|
<span class="browser-config-field-label">Keyboard variant</span>
|
|
<input
|
|
type="text"
|
|
x-model="$store.browserConfig.config.keyboard_variant"
|
|
placeholder="mac, nodeadkeys, colemak, ..."
|
|
autocomplete="off"
|
|
/>
|
|
<span class="browser-config-field-help">
|
|
XKB variant for the layout above. German Mac keyboards use mac, so Option+L types @.
|
|
<a href="https://man.archlinux.org/man/xkeyboard-config.7#LAYOUTS" target="_blank" rel="noreferrer">Variants per layout</a>. Changing it restarts the internal browser on save.
|
|
</span>
|
|
</label>
|
|
|
|
<label class="browser-config-switch-row">
|
|
<span class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Autofocus active page</span>
|
|
<span class="browser-config-field-help">Update the visible Browser surface for pages opened or changed by Browser tool results.</span>
|
|
</span>
|
|
<span class="browser-config-toggle-with-label">
|
|
<span class="browser-config-toggle-label" x-text="$store.browserConfig.autofocusLabel()"></span>
|
|
<span class="browser-config-toggle">
|
|
<input
|
|
type="checkbox"
|
|
:checked="$store.browserConfig.config.autofocus_active_page !== false"
|
|
@change="$store.browserConfig.setAutofocusActivePage($event.target.checked)"
|
|
/>
|
|
<span class="browser-config-switch"></span>
|
|
</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="browser-config-card browser-config-management">
|
|
<div class="section-title">Browsing data</div>
|
|
<div class="section-description">Manage the container browser's shared profile. These pages open in Browser and their changes take effect immediately.</div>
|
|
<div class="browser-config-management-row">
|
|
<div class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Clear browsing data</span>
|
|
<span class="browser-config-field-help">Choose a time range and remove history, cookies, cached files, or other site data.</span>
|
|
</div>
|
|
<button type="button" class="button" :disabled="!!$store.browserConfig.openingBrowserPage"
|
|
@click="$store.browserConfig.openBrowserPage('clearData')">Clear browsing data</button>
|
|
</div>
|
|
<div class="browser-config-management-row">
|
|
<div class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Browsing history</span>
|
|
<span class="browser-config-field-help">Search and manage pages visited in the container browser.</span>
|
|
</div>
|
|
<button type="button" class="button" aria-label="Manage browsing history" :disabled="!!$store.browserConfig.openingBrowserPage"
|
|
@click="$store.browserConfig.openBrowserPage('history')">Manage</button>
|
|
</div>
|
|
<label class="browser-config-management-row">
|
|
<span class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Annotation screenshots</span>
|
|
<span class="browser-config-field-help">Include a page screenshot with each annotation. Images help explain visual changes and use more model context.</span>
|
|
</span>
|
|
<select class="browser-config-choice" x-model.boolean="config.annotation_screenshots" aria-label="Annotation screenshots">
|
|
<option value="false">Do not include</option>
|
|
<option value="true">Always include</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="browser-config-card browser-config-management">
|
|
<div class="section-title">Autofill and passwords</div>
|
|
<div class="browser-config-management-row">
|
|
<div class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Password manager</span>
|
|
<span class="browser-config-field-help">Manage saved passwords, imports, and sign-in preferences in the container browser.</span>
|
|
</div>
|
|
<button type="button" class="button" aria-label="Manage passwords" :disabled="!!$store.browserConfig.openingBrowserPage"
|
|
@click="$store.browserConfig.openBrowserPage('passwords')">Manage</button>
|
|
</div>
|
|
<div class="browser-config-management-row">
|
|
<div class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Contact info</span>
|
|
<span class="browser-config-field-help">Add, edit, and delete saved addresses, phone numbers, and email addresses.</span>
|
|
</div>
|
|
<button type="button" class="button" aria-label="Manage contact info" :disabled="!!$store.browserConfig.openingBrowserPage"
|
|
@click="$store.browserConfig.openBrowserPage('addresses')">Manage</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="browser-config-card browser-config-management">
|
|
<div class="section-title">Downloads</div>
|
|
<div class="browser-config-management-row">
|
|
<div class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Location and download prompts</span>
|
|
<span class="browser-config-field-help">Choose a folder inside the container and whether to ask where to save each file.</span>
|
|
</div>
|
|
<button type="button" class="button" aria-label="Change download settings" :disabled="!!$store.browserConfig.openingBrowserPage"
|
|
@click="$store.browserConfig.openBrowserPage('downloadSettings')">Change</button>
|
|
</div>
|
|
<div class="browser-config-management-row">
|
|
<div class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Download history</span>
|
|
<span class="browser-config-field-help">View and manage downloads from the container browser.</span>
|
|
</div>
|
|
<button type="button" class="button" aria-label="Manage download history" :disabled="!!$store.browserConfig.openingBrowserPage"
|
|
@click="$store.browserConfig.openBrowserPage('downloads')">Manage</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="browser-config-card">
|
|
<div class="section-title">Extensions</div>
|
|
<div class="section-description">
|
|
Choose which installed Chrome extensions Browser loads.
|
|
</div>
|
|
|
|
<div class="browser-config-management-row">
|
|
<div class="browser-config-switch-copy">
|
|
<span class="browser-config-field-label">Extension manager</span>
|
|
<span class="browser-config-field-help">Open Chromium's extension details, permissions, and developer tools.</span>
|
|
</div>
|
|
<button type="button" class="button" aria-label="Manage Chrome extensions" :disabled="!!$store.browserConfig.openingBrowserPage"
|
|
@click="$store.browserConfig.openBrowserPage('extensions')">Manage</button>
|
|
</div>
|
|
<div class="browser-config-extension-install">
|
|
<label class="browser-config-field">
|
|
<span class="browser-config-field-label">Chrome Web Store URL</span>
|
|
<input type="text" x-model="$store.browserConfig.extensionInstallUrl"
|
|
@keydown.enter.prevent="$store.browserConfig.installExtensionFromUrl()"
|
|
placeholder="https://chromewebstore.google.com/detail/..." autocomplete="off" />
|
|
</label>
|
|
<div class="browser-config-install-actions">
|
|
<button type="button" class="button" @click="$store.browserConfig.installExtensionFromUrl()"
|
|
:disabled="$store.browserConfig.extensionActionLoading"
|
|
:aria-busy="$store.browserConfig.extensionActionLoading.toString()">
|
|
<x-icon :class="{ spinning: $store.browserConfig.extensionActionLoading }"
|
|
:name="$store.browserConfig.extensionActionLoading ? 'progress_activity' : 'download'"></x-icon>
|
|
<span x-text="$store.browserConfig.extensionActionLoading ? 'Installing…' : 'Install URL'">Install URL</span>
|
|
</button>
|
|
<button type="button" class="button" @click="$store.browserConfig.askAgentInstallExtension(context)">
|
|
<x-icon name="psychology_alt"></x-icon><span>Scan with A0</span>
|
|
</button>
|
|
<button type="button" class="button" @click="$store.browserConfig.createExtensionWithAgent(context)">
|
|
<x-icon name="add_circle"></x-icon><span>Create New Extension with A0</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="browser-config-warning">
|
|
<x-icon name="warning"></x-icon>
|
|
<span>
|
|
Extensions run inside the Docker browser sandbox, but malicious or buggy extensions can
|
|
still damage that environment. Keep only the extensions you trust enabled.
|
|
</span>
|
|
</div>
|
|
|
|
<div class="browser-config-extension-list">
|
|
<div class="browser-config-subtitle">
|
|
<span>Installed extensions</span>
|
|
<x-icon class="spinning" x-show="$store.browserConfig.extensionsLoading" name="progress_activity"></x-icon>
|
|
</div>
|
|
<template x-if="!$store.browserConfig.extensionsLoading && !$store.browserConfig.extensionsList.length">
|
|
<div class="browser-config-empty">No installed extensions found.</div>
|
|
</template>
|
|
<template x-for="extension in $store.browserConfig.extensionsList" :key="extension.path">
|
|
<div class="browser-config-extension-row" :title="extension.path">
|
|
<span class="browser-config-extension-text">
|
|
<span class="browser-config-extension-name" x-text="extension.name || 'Unnamed extension'"></span>
|
|
<span class="browser-config-extension-meta" x-text="$store.browserConfig.extensionVersionLabel(extension)"></span>
|
|
</span>
|
|
<span class="browser-config-extension-actions">
|
|
<label class="browser-config-toggle">
|
|
<input
|
|
type="checkbox"
|
|
:checked="$store.browserConfig.extensionEnabled(extension)"
|
|
:aria-label="'Enable ' + (extension.name || 'extension')"
|
|
:disabled="$store.browserConfig.extensionDeleteLoadingPath === extension.path"
|
|
@change="$store.browserConfig.setExtensionEnabled(extension, $event.target.checked)"
|
|
/>
|
|
<span class="browser-config-switch"></span>
|
|
</label>
|
|
<button
|
|
type="button"
|
|
class="browser-config-extension-delete"
|
|
:disabled="!$store.browserConfig.extensionCanDelete(extension) || $store.browserConfig.extensionDeleteLoadingPath === extension.path"
|
|
:title="$store.browserConfig.extensionDeleteTitle(extension)"
|
|
:aria-label="'Delete ' + (extension.name || 'extension')"
|
|
@click.stop="$store.browserConfig.deleteExtension(extension)"
|
|
>
|
|
<x-icon
|
|
:class="{ spinning: $store.browserConfig.extensionDeleteLoadingPath === extension.path }"
|
|
:name="$store.browserConfig.extensionDeleteLoadingPath === extension.path ? 'progress_activity' : 'delete'"
|
|
></x-icon>
|
|
</button>
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="browser-config-pill-row">
|
|
<span class="browser-config-pill" x-text="$store.browserConfig.pathCountLabel()"></span>
|
|
<span class="browser-config-pill tone-active" x-show="$store.browserConfig.extensionModeReady()">
|
|
Ready
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<style>
|
|
.browser-config-sections {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.browser-config-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
padding: 16px;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.browser-config-management { gap: 0; }
|
|
.browser-config-management > .section-title { margin-bottom: 8px; }
|
|
.browser-config-management > .section-description { margin-bottom: 6px; }
|
|
.browser-config-management-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 20px;
|
|
padding: 14px 0;
|
|
}
|
|
.browser-config-management-row + .browser-config-management-row {
|
|
border-top: 1px solid var(--color-border);
|
|
}
|
|
.browser-config-management-row:last-child { padding-bottom: 0; }
|
|
.browser-config-management-row > .button { flex-shrink: 0; }
|
|
.browser-config-management-row > .browser-config-switch-copy { flex: 1; min-width: 0; }
|
|
.browser-config-choice {
|
|
flex-shrink: 0;
|
|
width: 190px;
|
|
max-width: 100%;
|
|
min-height: 36px;
|
|
padding: 7px 10px;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 8px;
|
|
background: var(--color-input);
|
|
color: var(--color-text);
|
|
font: inherit;
|
|
font-size: 0.84rem;
|
|
}
|
|
.browser-config-install-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
.browser-config-install-actions .button { display: inline-flex; align-items: center; gap: 6px; }
|
|
.browser-config-management-row button:focus-visible,
|
|
.browser-config-choice:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
|
|
@media (max-width: 540px) {
|
|
.browser-config-management-row { flex-wrap: wrap; gap: 10px; }
|
|
.browser-config-management-row > .browser-config-switch-copy { flex-basis: 100%; }
|
|
}
|
|
|
|
.browser-config-field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.browser-config-field-label {
|
|
color: var(--color-text);
|
|
font-size: 0.84rem;
|
|
font-weight: 650;
|
|
}
|
|
|
|
.browser-config-field-help {
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.78rem;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.browser-config-proxy {
|
|
overflow: hidden;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.browser-config-proxy summary {
|
|
padding: 10px 12px;
|
|
color: var(--color-text);
|
|
font-size: 0.84rem;
|
|
font-weight: 650;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.browser-config-proxy-body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
padding: 14px;
|
|
border-top: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
|
|
}
|
|
|
|
.browser-config-field input[type="text"],
|
|
.browser-config-field input[type="password"],
|
|
.browser-config-field input[type="number"],
|
|
.browser-config-field select {
|
|
width: 100%;
|
|
min-height: 36px;
|
|
padding: 7px 10px;
|
|
border: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
|
|
border-radius: 8px;
|
|
background: var(--color-input);
|
|
color: var(--color-text);
|
|
font: inherit;
|
|
}
|
|
|
|
.browser-config-switch-row {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
align-items: center;
|
|
gap: 16px;
|
|
min-height: 46px;
|
|
padding-top: 2px;
|
|
}
|
|
|
|
.browser-config-switch-copy {
|
|
display: flex;
|
|
min-width: 0;
|
|
flex-direction: column;
|
|
gap: 3px;
|
|
}
|
|
|
|
.browser-config-toggle-with-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.browser-config-toggle-label {
|
|
min-width: 24px;
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.78rem;
|
|
font-weight: 650;
|
|
text-align: right;
|
|
}
|
|
|
|
.browser-config-extension-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding: 12px;
|
|
border: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
|
|
border-radius: 8px;
|
|
background: color-mix(in srgb, var(--color-panel) 78%, transparent);
|
|
}
|
|
|
|
.browser-config-subtitle {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.82rem;
|
|
font-weight: 650;
|
|
}
|
|
|
|
.browser-config-subtitle .material-symbols-outlined {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.browser-config-empty,
|
|
.browser-config-extension-meta {
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
.browser-config-extension-row {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
align-items: center;
|
|
gap: 14px;
|
|
min-height: 42px;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.browser-config-extension-row + .browser-config-extension-row {
|
|
border-top: 1px solid color-mix(in srgb, var(--color-border) 52%, transparent);
|
|
}
|
|
|
|
.browser-config-extension-text {
|
|
display: flex;
|
|
min-width: 0;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.browser-config-extension-name {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: var(--color-text);
|
|
font-weight: 650;
|
|
}
|
|
|
|
.browser-config-extension-actions {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.browser-config-toggle {
|
|
position: relative;
|
|
display: inline-flex;
|
|
flex: 0 0 auto;
|
|
align-items: center;
|
|
width: 42px;
|
|
height: 24px;
|
|
}
|
|
|
|
.browser-config-toggle input {
|
|
position: absolute;
|
|
inset: 0;
|
|
margin: 0;
|
|
opacity: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.browser-config-toggle input:disabled {
|
|
cursor: wait;
|
|
}
|
|
|
|
.browser-config-switch {
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--color-border) 78%, transparent);
|
|
pointer-events: none;
|
|
transition: background-color 0.18s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.browser-config-switch::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: 3px;
|
|
left: 3px;
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
background: var(--color-background);
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24);
|
|
transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.browser-config-toggle input:checked + .browser-config-switch {
|
|
background: color-mix(in srgb, var(--color-primary) 76%, #16a34a);
|
|
}
|
|
|
|
.browser-config-toggle input:checked + .browser-config-switch::after {
|
|
transform: translateX(18px);
|
|
}
|
|
|
|
.browser-config-toggle input:disabled + .browser-config-switch {
|
|
opacity: 0.58;
|
|
}
|
|
|
|
.browser-config-extension-delete {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 28px;
|
|
min-width: 28px;
|
|
height: 28px;
|
|
min-height: 28px;
|
|
padding: 0;
|
|
border: 1px solid color-mix(in srgb, #be123c 30%, var(--color-border));
|
|
border-radius: 7px;
|
|
background: transparent;
|
|
color: color-mix(in srgb, #be123c 78%, var(--color-text));
|
|
cursor: pointer;
|
|
}
|
|
|
|
.browser-config-extension-delete:hover:not(:disabled) {
|
|
background: color-mix(in srgb, #be123c 12%, var(--color-background));
|
|
color: #be123c;
|
|
}
|
|
|
|
.browser-config-extension-delete:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.48;
|
|
}
|
|
|
|
.browser-config-extension-delete .material-symbols-outlined {
|
|
font-size: 17px;
|
|
}
|
|
|
|
.browser-config-note {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
padding: 10px 12px;
|
|
border-radius: 8px;
|
|
background: color-mix(in srgb, var(--color-panel) 82%, transparent);
|
|
color: var(--color-text-secondary);
|
|
font-size: var(--font-size-small);
|
|
}
|
|
|
|
.browser-config-note.browser-config-success {
|
|
background: color-mix(in srgb, #15803d 12%, var(--color-background));
|
|
color: color-mix(in srgb, var(--color-text) 88%, #166534);
|
|
}
|
|
|
|
.browser-config-guide {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.browser-config-guide-heading,
|
|
.browser-config-guide-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
color: var(--color-text);
|
|
font-size: 0.84rem;
|
|
font-weight: 650;
|
|
}
|
|
|
|
.browser-config-guide-heading .material-symbols-outlined,
|
|
.browser-config-guide-title .material-symbols-outlined {
|
|
color: var(--color-primary);
|
|
font-size: 19px;
|
|
}
|
|
|
|
.browser-config-guide-title small {
|
|
margin-left: 4px;
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.68rem;
|
|
font-weight: 550;
|
|
}
|
|
|
|
.browser-config-guide-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 10px;
|
|
}
|
|
|
|
.browser-config-guide-item {
|
|
padding: 11px;
|
|
border: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
|
|
border-radius: 8px;
|
|
background: color-mix(in srgb, var(--color-background) 82%, transparent);
|
|
}
|
|
|
|
.browser-config-guide-item ol {
|
|
margin: 9px 0 0;
|
|
padding-left: 1.2rem;
|
|
color: var(--color-text-secondary);
|
|
font-size: var(--font-size-small);
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.browser-config-guide-item li + li {
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.browser-config-guide-item p,
|
|
.browser-config-guide-details p {
|
|
margin: 8px 0 0;
|
|
color: var(--color-text-secondary);
|
|
font-size: var(--font-size-small);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.browser-config-setup-buttons {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 7px;
|
|
margin-top: 9px;
|
|
}
|
|
|
|
.browser-config-setup-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 5px;
|
|
min-height: 30px;
|
|
padding: 4px 10px;
|
|
border: 1px solid color-mix(in srgb, var(--color-primary) 36%, var(--color-border));
|
|
border-radius: 7px;
|
|
background: color-mix(in srgb, var(--color-primary) 7%, var(--color-panel));
|
|
color: var(--color-text);
|
|
font: inherit;
|
|
font-size: var(--font-size-small);
|
|
font-weight: 650;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.browser-config-setup-button:hover:not(:disabled) {
|
|
border-color: color-mix(in srgb, var(--color-primary) 72%, var(--color-border));
|
|
background: color-mix(in srgb, var(--color-primary) 13%, var(--color-panel));
|
|
}
|
|
|
|
.browser-config-setup-button:focus-visible {
|
|
outline: 2px solid color-mix(in srgb, var(--color-primary) 72%, transparent);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.browser-config-setup-button:disabled {
|
|
cursor: wait;
|
|
opacity: 0.58;
|
|
}
|
|
|
|
.browser-config-setup-button .material-symbols-outlined {
|
|
color: var(--color-primary);
|
|
font-size: 15px;
|
|
}
|
|
|
|
.browser-config-guide-item .browser-config-guide-support {
|
|
color: color-mix(in srgb, var(--color-text-secondary) 88%, var(--color-primary));
|
|
}
|
|
|
|
.browser-config-guide-details {
|
|
border-top: 1px solid color-mix(in srgb, var(--color-border) 64%, transparent);
|
|
}
|
|
|
|
.browser-config-guide-details summary {
|
|
padding-top: 10px;
|
|
color: var(--color-text-secondary);
|
|
font-size: var(--font-size-small);
|
|
font-weight: 650;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.browser-config-guide-details code {
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.browser-config-warning {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 9px;
|
|
padding: 11px 12px;
|
|
border: 1px solid color-mix(in srgb, #d97706 44%, var(--color-border));
|
|
border-radius: 8px;
|
|
background: color-mix(in srgb, #d97706 14%, var(--color-background));
|
|
color: color-mix(in srgb, var(--color-text) 86%, #92400e);
|
|
font-size: var(--font-size-small);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.browser-config-warning .material-symbols-outlined {
|
|
color: #b45309;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.browser-config-sections .spinning {
|
|
display: inline-block;
|
|
transform-origin: center;
|
|
animation: browser-config-spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes browser-config-spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.browser-config-pill-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
.browser-config-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
min-height: 28px;
|
|
padding: 0 10px;
|
|
border-radius: 999px;
|
|
border: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
|
|
background: color-mix(in srgb, var(--color-panel) 88%, transparent);
|
|
font-size: 0.78rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.browser-config-pill.tone-active {
|
|
color: #1b5e20;
|
|
border-color: rgba(27, 94, 32, 0.18);
|
|
background: rgba(46, 125, 50, 0.12);
|
|
}
|
|
</style>
|
|
</body>
|
|
</html>
|