Compare commits

...
11 Commits
Author SHA1 Message Date
wt 4b6f51a3e5 bump version to 20261009.1
Build and publish extension / Build extension packages (push) Successful in 1m1s
Build and publish extension / Publish stores and attach release assets (push) Skipped
Build and publish extension / Build extension packages (release) Successful in 1m8s
Build and publish extension / Publish stores and attach release assets (release) Failing after 10m52s
2026-10-09 18:00:43 +07:00
wt b8046e7b9a fix warnings in firefox addons
Build and publish extension / Build extension packages (push) Canceled after 0s
Build and publish extension / Publish stores and attach release assets (push) Canceled after 0s
Build and publish extension / Build extension packages (release) Canceled after 0s
Build and publish extension / Publish stores and attach release assets (release) Canceled after 0s
2026-10-09 17:58:27 +07:00
wt 41456c5c0f added screenshots and fix manifest.json
Build and publish extension / Publish stores and attach release assets (push) Canceled after 0s
Build and publish extension / Build extension packages (push) Canceled after 32s
2026-10-09 17:46:46 +07:00
wt dd50029bc5 updated LICENSE and fix ci/cd №3
Build and publish extension / Build extension packages (push) Successful in 1m35s
Build and publish extension / Publish stores and attach release assets (push) Skipped
2026-10-09 17:30:31 +07:00
wt 4dd5f48e3b fix ci/cd №2
Build and publish extension / Build extension packages (push) Successful in 1m1s
Build and publish extension / Publish stores and attach release assets (push) Skipped
2026-10-09 17:14:57 +07:00
wt 9e2ef513a0 fix ci/cd
Build and publish extension / Build extension packages (push) Successful in 51s
Build and publish extension / Publish stores and attach release assets (push) Skipped
2026-10-09 17:01:22 +07:00
wt 319bcdefbd fix error if no secrets/credentials
Build and publish extension / Build extension packages (push) Successful in 50s
Build and publish extension / Publish stores and attach release assets (push) Skipped
2026-10-09 16:54:27 +07:00
wt 07a0f2c1e3 bump to version 20261009
Build and publish extension / Build extension packages (push) Successful in 50s
Build and publish extension / Publish stores and attach release assets (push) Skipped
2026-10-09 16:41:56 +07:00
wt e88fb193df fix rsync
Build and publish extension / Build extension packages (push) Successful in 50s
Build and publish extension / Publish stores and attach release assets (push) Skipped
2026-10-09 16:38:53 +07:00
wt 2f5568fb37 ci: build and publish extension packages from GitHub Actions
Build and publish extension / Build extension packages (push) Failing after 2m2s
Build and publish extension / Publish stores and attach release assets (push) Skipped
- build Firefox XPI and Chrome ZIP on every push
- upload packages as downloadable workflow artifacts
- sign and submit Firefox releases to AMO
- upload and publish Chrome releases to Chrome Web Store
- attach signed packages to GitHub release cards
- document required publishing secrets and release workflow
2026-10-09 16:34:05 +07:00
wt 1926ac166b ci: build and publish extension packages from GitHub Actions
- build Firefox XPI and Chrome ZIP on every push
- upload packages as downloadable workflow artifacts
- sign and submit Firefox releases to AMO
- upload and publish Chrome releases to Chrome Web Store
- attach signed packages to GitHub release cards
- document required publishing secrets and release workflow
2026-10-09 16:34:02 +07:00
11 changed files with 644 additions and 233 deletions

No files matched your search

+245
View File
@@ -0,0 +1,245 @@
name: Build and publish extension
on:
push:
branches:
- "**"
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: "Version to validate (for example 20261009.1)"
required: true
type: string
permissions:
contents: read
jobs:
build:
name: Build extension packages
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name || github.ref }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Validate release version
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
env:
RELEASE_VERSION: ${{ github.event.release.tag_name || inputs.version }}
run: |
python - <<'PY'
import json, os, re
expected = os.environ["RELEASE_VERSION"].removeprefix("v")
for path in ("manifest.json", "manifest.chrome.json"):
actual = json.load(open(path, encoding="utf-8"))["version"]
if actual != expected:
raise SystemExit(f"{path}: version {actual!r} does not match release {expected!r}")
if not re.fullmatch(r"\d+(\.\d+){0,3}", expected):
raise SystemExit(f"Release version must be numeric, got {expected!r}")
PY
- name: Install web-ext
run: npm install --global web-ext
- name: Prepare Firefox source
run: |
python - <<'PY'
from pathlib import Path
import shutil
source = Path('.')
target = Path('dist/firefox-source')
excluded = {'.git', '.github', 'dist', 'web-ext-artifacts', 'scripts',
'manifest.chrome.json', 'background-chrome.js',
'background-service-worker.js', 'browser-api.js'}
target.mkdir(parents=True, exist_ok=True)
for item in source.iterdir():
if item.name in excluded:
continue
destination = target / item.name
if item.is_dir():
shutil.copytree(item, destination, dirs_exist_ok=True)
else:
shutil.copy2(item, destination)
PY
- name: Build Firefox package
run: |
web-ext build \
--source-dir dist/firefox-source \
--artifacts-dir dist/firefox \
--filename proxyswitcherforkbywt-firefox.xpi \
--overwrite-dest
- name: Build Chrome package
run: python scripts/build-chrome.py
- name: Upload packages for this push
uses: actions/upload-artifact@v4
with:
name: extension-packages-${{ github.sha }}
if-no-files-found: error
path: |
dist/firefox/proxyswitcherforkbywt-firefox.xpi
web-ext-artifacts/proxyswitcherforkbywt-chrome.zip
publish:
name: Publish stores and attach release assets
if: github.event_name == 'release' && github.event.action == 'published'
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
env:
AMO_JWT_ISSUER: ${{ secrets.AMO_JWT_ISSUER }}
AMO_JWT_SECRET: ${{ secrets.AMO_JWT_SECRET }}
CWS_PUBLISHER_ID: ${{ secrets.CWS_PUBLISHER_ID }}
CWS_EXTENSION_ID: ${{ secrets.CWS_EXTENSION_ID }}
CWS_CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }}
CWS_CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }}
CWS_REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }}
steps:
- name: Check out release source
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Check store credentials
id: credentials
run: |
python - <<'PY'
import os
stores = {
'firefox': ('AMO_JWT_ISSUER', 'AMO_JWT_SECRET'),
'chrome': ('CWS_PUBLISHER_ID', 'CWS_EXTENSION_ID', 'CWS_CLIENT_ID',
'CWS_CLIENT_SECRET', 'CWS_REFRESH_TOKEN'),
}
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
for store, names in stores.items():
missing = [name for name in names if not os.environ.get(name, '').strip()]
output.write(f"{store}={'false' if missing else 'true'}\n")
if missing:
print(f"Skipping {store} deployment: missing credentials: {', '.join(missing)}")
else:
print(f"{store} deployment enabled")
PY
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Download build packages
uses: actions/download-artifact@v4
with:
name: extension-packages-${{ github.sha }}
path: dist/packages
- name: Set up Firefox signing tools
if: steps.credentials.outputs.firefox == 'true'
run: |
npm install --global web-ext
python - <<'PY'
from pathlib import Path
import shutil
source = Path('.')
target = Path('dist/firefox-source')
excluded = {'.git', '.github', 'dist', 'web-ext-artifacts', 'scripts',
'manifest.chrome.json', 'background-chrome.js',
'background-service-worker.js', 'browser-api.js'}
target.mkdir(parents=True, exist_ok=True)
for item in source.iterdir():
if item.name in excluded:
continue
destination = target / item.name
if item.is_dir():
shutil.copytree(item, destination, dirs_exist_ok=True)
else:
shutil.copy2(item, destination)
PY
- name: Sign and submit Firefox package to AMO
if: steps.credentials.outputs.firefox == 'true'
run: |
test -n "$AMO_JWT_ISSUER" || { echo 'Missing AMO_JWT_ISSUER secret'; exit 1; }
test -n "$AMO_JWT_SECRET" || { echo 'Missing AMO_JWT_SECRET secret'; exit 1; }
web-ext sign \
--source-dir dist/firefox-source \
--artifacts-dir dist/firefox-signed \
--api-key "$AMO_JWT_ISSUER" \
--api-secret "$AMO_JWT_SECRET" \
--amo-metadata amo-metadata.json \
--channel listed \
--approval-timeout 900000 \
--no-input
firefox_xpi=$(find dist/firefox-signed -maxdepth 1 -name '*.xpi' -print -quit)
test -n "$firefox_xpi" || { echo 'AMO did not return a signed XPI'; exit 1; }
cp "$firefox_xpi" dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
- name: Upload Chrome package to Chrome Web Store
if: steps.credentials.outputs.chrome == 'true'
run: |
set -euo pipefail
for name in CWS_PUBLISHER_ID CWS_EXTENSION_ID CWS_CLIENT_ID CWS_CLIENT_SECRET CWS_REFRESH_TOKEN; do
test -n "${!name}" || { echo "Missing $name secret"; exit 1; }
done
token=$(curl --fail --silent --show-error \
--data-urlencode "client_id=$CWS_CLIENT_ID" \
--data-urlencode "client_secret=$CWS_CLIENT_SECRET" \
--data-urlencode "refresh_token=$CWS_REFRESH_TOKEN" \
--data-urlencode 'grant_type=refresh_token' \
https://oauth2.googleapis.com/token | python -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')
chrome_zip=$(find dist/packages -name 'proxyswitcherforkbywt-chrome.zip' -print -quit)
test -f "$chrome_zip" || { echo "Missing Chrome package: $chrome_zip"; exit 1; }
curl --fail --silent --show-error \
-X POST \
-H "Authorization: Bearer $token" \
-H 'Content-Type: application/zip' \
--data-binary "@$chrome_zip" \
"https://chromewebstore.googleapis.com/upload/v2/publishers/$CWS_PUBLISHER_ID/items/$CWS_EXTENSION_ID:upload"
curl --fail --silent --show-error \
-X POST \
-H "Authorization: Bearer $token" \
"https://chromewebstore.googleapis.com/v2/publishers/$CWS_PUBLISHER_ID/items/$CWS_EXTENSION_ID:publish"
- name: Prepare release assets
run: |
mkdir -p dist/release-assets
chrome_zip=$(find dist/packages -name 'proxyswitcherforkbywt-chrome.zip' -print -quit)
test -f "$chrome_zip"
cp "$chrome_zip" dist/release-assets/
firefox_xpi=dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
if [ ! -f "$firefox_xpi" ]; then
firefox_xpi=$(find dist/packages -name 'proxyswitcherforkbywt-firefox.xpi' -print -quit)
fi
test -f "$firefox_xpi"
cp "$firefox_xpi" dist/release-assets/
- name: Attach Firefox and Chrome packages to release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
gh release upload "$RELEASE_TAG" \
dist/release-assets/* \
--clobber
- name: Upload release packages as workflow artifacts
uses: actions/upload-artifact@v4
with:
name: release-extension-packages-${{ github.event.release.tag_name }}
if-no-files-found: error
path: dist/release-assets/*
+5 -1
View File
@@ -16,18 +16,22 @@ This repository contains the Firefox WebExtension **Proxy Switcher Fork By WT**.
- `options.html`, `options.js`, `options.css` — profiles, domains, management actions, and Advanced Settings. - `options.html`, `options.js`, `options.css` — profiles, domains, management actions, and Advanced Settings.
- `icons/` — extension icon sizes referenced by `manifest.json`. - `icons/` — extension icon sizes referenced by `manifest.json`.
- `Makefile` — build and cleanup commands. - `Makefile` — build and cleanup commands.
- `.github/workflows/release.yml` — per-push package builds, workflow artifacts, and release-triggered AMO/Chrome Web Store publishing.
## Development rules ## Development rules
- Keep all configuration local. Do not add telemetry, remote requests, analytics, or external script dependencies. - Keep all configuration local. Do not add telemetry, remote requests, analytics, or external script dependencies.
- Preserve the `browser.*` WebExtension API style in shared UI code. Firefox uses Manifest V2 and `proxy.onRequest`; Chromium uses Manifest V3, `chrome.proxy.settings`, and a PAC script. - Preserve the `browser.*` WebExtension API style in shared UI code. Firefox uses Manifest V2 and `proxy.onRequest`; Chromium uses Manifest V3, `chrome.proxy.settings`, and a PAC script.
- Keep the Firefox Android minimum version at 113 or newer when using the `gecko_android` manifest key. - Keep the Firefox minimum at 140 and Firefox Android minimum at 142 when using built-in `data_collection_permissions` consent.
- Keep the Firefox and Chromium manifests as separate build targets. Do not replace the Firefox proxy listener with the Chrome PAC implementation. - Keep the Firefox and Chromium manifests as separate build targets. Do not replace the Firefox proxy listener with the Chrome PAC implementation.
- Proxy settings are domain-specific. A missing domain entry means direct connection; an entry with `enabled: false` is configured but disabled. - Proxy settings are domain-specific. A missing domain entry means direct connection; an entry with `enabled: false` is configured but disabled.
- Keep popup and options theme behavior consistent. Supported theme values are `system`, `light`, and `dark`. - Keep popup and options theme behavior consistent. Supported theme values are `system`, `light`, and `dark`.
- When changing an icon, update every size referenced by the manifest and verify the XPI contains the new paths. - When changing an icon, update every size referenced by the manifest and verify the XPI contains the new paths.
- Never hand-edit or retain stale `META-INF` signatures after changing files. Build artifacts must be signed again before distribution. - Never hand-edit or retain stale `META-INF` signatures after changing files. Build artifacts must be signed again before distribution.
- Do not commit generated `web-ext-artifacts/` packages unless explicitly requested. - Do not commit generated `web-ext-artifacts/` packages unless explicitly requested.
- Keep publishing credentials only in GitHub Secrets. The release workflow expects AMO JWT credentials and Chrome Web Store OAuth credentials.
- Keep `amo-metadata.json` valid for AMO listed submissions; keep `version.license` consistent with the MIT `LICENSE` file.
- Missing or incomplete store credentials must skip only that store's deployment. Keep package builds, release assets, and workflow artifacts available without store credentials.
## Validation ## Validation
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 WT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+23 -3
View File
@@ -48,7 +48,7 @@ The manifest requests these permissions:
- `webRequest` and `webRequestBlocking` — provide proxy authentication credentials. - `webRequest` and `webRequestBlocking` — provide proxy authentication credentials.
- `<all_urls>` — apply the domain routing policy to web requests. - `<all_urls>` — apply the domain routing policy to web requests.
These permissions are used for proxy management. The extension does not collect or transmit data. These permissions are used for proxy management. The manifest declares `data_collection_permissions.required` as `none`; the extension does not collect or transmit data.
## Installation ## Installation
@@ -66,7 +66,7 @@ For a normal install, use an XPI signed by Mozilla/AMO. Firefox may reject an un
Firefox Android requires a signed extension package. Submit the build through [addons.mozilla.org/developers](https://addons.mozilla.org/developers/) and download the signed XPI after review, or follow Mozilla's [self-distributed extension instructions](https://extensionworkshop.com/documentation/publish/install-self-distributed/). The manifest includes `browser_specific_settings.gecko_android` so AMO can mark the extension as Android-compatible. Firefox Android requires a signed extension package. Submit the build through [addons.mozilla.org/developers](https://addons.mozilla.org/developers/) and download the signed XPI after review, or follow Mozilla's [self-distributed extension instructions](https://extensionworkshop.com/documentation/publish/install-self-distributed/). The manifest includes `browser_specific_settings.gecko_android` so AMO can mark the extension as Android-compatible.
The manifest targets Firefox Android 113 or newer. The `proxy.onRequest` API is supported there, but the extension still needs to be tested on the target Firefox version and device. The manifest targets Firefox 140+ on desktop and Firefox Android 142+. The `proxy.onRequest` API is supported there, but the extension still needs to be tested on the target Firefox version and device.
### Chrome and Chromium desktop ### Chrome and Chromium desktop
@@ -94,6 +94,22 @@ The package is created at `web-ext-artifacts/proxyswitcherforkbywt.xpi`. This lo
For Chrome/Chromium, run `make build-chrome`. That creates `web-ext-artifacts/proxyswitcherforkbywt-chrome.zip` from `manifest.chrome.json` and the shared UI files. For Chrome/Chromium, run `make build-chrome`. That creates `web-ext-artifacts/proxyswitcherforkbywt-chrome.zip` from `manifest.chrome.json` and the shared UI files.
## GitHub Actions release publishing
`.github/workflows/release.yml` builds both packages on every push. The run exposes a downloadable `extension-packages-<commit>` artifact containing the Firefox XPI and Chrome ZIP. When a GitHub Release is published, it checks that both manifests match the release tag, submits the Firefox package to AMO for signing and review, uploads and submits the Chrome package through the Chrome Web Store API v2, and attaches the signed Firefox XPI and Chrome ZIP to the release card.
Configure these repository or environment secrets before publishing:
- `AMO_JWT_ISSUER` and `AMO_JWT_SECRET` — AMO API credentials for `web-ext sign`.
- `CWS_PUBLISHER_ID` and `CWS_EXTENSION_ID` — Chrome Web Store publisher and existing item IDs.
- `CWS_CLIENT_ID`, `CWS_CLIENT_SECRET`, and `CWS_REFRESH_TOKEN` — Google OAuth credentials with Chrome Web Store API access.
Each store is optional: deployment runs only when its full set of credentials is present. Missing or incomplete credentials skip that store without failing the workflow or skipping the other store. Both packages are still built and attached to the release. If Firefox deployment is skipped, the release contains the unsigned Firefox XPI for development instead of a signed XPI. Invalid credentials or actual store API failures still fail the deployment step.
AMO can create the initial listing automatically from `amo-metadata.json` through `web-ext sign --channel listed`; manually uploading the first Firefox version is not required. Chrome Web Store requires an existing item and completed listing/privacy information. Store review still applies; the workflow submits the update and does not bypass review. Never commit any of these credentials.
The Firefox workflow passes [`amo-metadata.json`](amo-metadata.json) to AMO. It declares the MIT license, categories, and listing metadata for a listed submission.
To remove generated packages: To remove generated packages:
```sh ```sh
@@ -112,4 +128,8 @@ python -m json.tool manifest.json >/dev/null
## Project version ## Project version
Current version: **1.0.1** Current version: **20261009.1**
## License
Distributed under the [MIT License](LICENSE).
+16
View File
@@ -0,0 +1,16 @@
{
"version": {
"license": "MIT"
},
"default_locale": "en-US",
"name": {
"en-US": "Proxy Switcher Fork By WT"
},
"categories": {
"firefox": ["other"],
"android": ["other"]
},
"summary": {
"en-US": "Route selected domains through HTTP or SOCKS5 proxy servers."
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Proxy Switcher Fork By WT", "name": "Proxy Switcher Fork By WT",
"version": "1.0.1", "version": "20261009.1",
"description": "Selectively apply proxy servers to specific domains.", "description": "Selectively apply proxy servers to specific domains.",
"minimum_chrome_version": "99", "minimum_chrome_version": "99",
"icons": { "icons": {
+7 -4
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Proxy Switcher Fork By WT", "name": "Proxy Switcher Fork By WT",
"version": "1.0.1", "version": "20261009.1",
"description": "Selectively apply proxy servers to specific domains. All settings are stored locally and no data is collected or transmitted externally.", "description": "Selectively apply proxy servers to specific domains. All settings are stored locally and no data is collected or transmitted externally.",
"icons": { "icons": {
"16": "icons/proxy-switcher-16.png", "16": "icons/proxy-switcher-16.png",
@@ -35,11 +35,14 @@
}, },
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
"id": "proxy-switcher-for-by-wt@9c0b2d3d", "id": "proxy-switcher-fork-by-wt@9c0b2d3d",
"strict_min_version": "91.1.0" "strict_min_version": "140.0",
"data_collection_permissions": {
"required": ["none"]
}
}, },
"gecko_android": { "gecko_android": {
"strict_min_version": "113.0" "strict_min_version": "142.0"
} }
} }
} }
+326 -224
View File
@@ -1,249 +1,351 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Proxy Switcher - Options</title> <title>Proxy Switcher - Options</title>
<link rel="stylesheet" href="options.css"> <link rel="stylesheet" href="options.css" />
</head> </head>
<body> <body>
<!-- Modal Overlay --> <!-- Modal Overlay -->
<div id="modal-overlay" class="modal-overlay hidden"></div> <div id="modal-overlay" class="modal-overlay hidden"></div>
<div class="container"> <div class="container">
<header> <header>
<h1><img src="icons/proxy-switcher-64.png" alt="Proxy Switcher"> Proxy Switcher</h1> <h1>
<p class="subtitle">Manage domain-specific proxy settings</p> <img
</header> src="icons/proxy-switcher-64.png"
alt="Proxy Switcher"
/>
Proxy Switcher
</h1>
<p class="subtitle">Manage domain-specific proxy settings</p>
</header>
<main> <main>
<section class="profiles-section"> <section class="profiles-section">
<div class="section-header"> <div class="section-header">
<h2>Proxy Profiles</h2> <h2>Proxy Profiles</h2>
<button id="add-profile" class="btn btn-primary"> <button id="add-profile" class="btn btn-primary">
<span class="icon">+</span> Add Profile <span class="icon">+</span> Add Profile
</button> </button>
</div>
<div id="profiles-list" class="profiles-list">
<!-- Dynamically populated -->
</div>
</section>
<section class="domains-section">
<div class="section-header">
<h2>Configured Domains</h2>
<button id="add-domain" class="btn btn-primary">
<span class="icon">+</span> Add Domain
</button>
</div>
<div class="search-controls">
<div class="search-group">
<input type="text" id="domain-search" placeholder="Search domains...">
<select id="tag-filter">
<option value="">All tags</option>
<!-- Tags will be added dynamically -->
</select>
</div>
</div>
<div id="domains-list" class="domains-list">
<!-- Dynamically populated -->
</div>
</section>
<section id="domain-form-section" class="domain-form-section hidden">
<h2 id="form-title">Add Domain</h2>
<form id="domain-form" class="domain-form">
<div class="form-group">
<label for="domain-input">Domain *</label>
<input type="text" id="domain-input"
placeholder="example.com"
pattern="^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$"
required>
<small class="form-hint">Enter domain without protocol (e.g., example.com)</small>
</div> </div>
<div class="form-group"> <div id="profiles-list" class="profiles-list">
<label for="proxy-type">Proxy Type *</label> <!-- Dynamically populated -->
<select id="proxy-type" required> </div>
<option value="http">HTTP</option> </section>
<option value="socks">SOCKS5</option>
</select> <section class="domains-section">
<div class="section-header">
<h2>Configured Domains</h2>
<button id="add-domain" class="btn btn-primary">
<span class="icon">+</span> Add Domain
</button>
</div> </div>
<div class="form-group"> <div class="search-controls">
<label for="profile-select">Use Profile (optional)</label> <div class="search-group">
<select id="profile-select"> <input
<option value="">-- Select Profile --</option> type="text"
<!-- Profiles will be added dynamically --> id="domain-search"
</select> placeholder="Search domains..."
<small class="form-hint">Selecting a profile will fill the fields below</small> />
</div> <select id="tag-filter">
<option value="">All tags</option>
<div class="form-row"> <!-- Tags will be added dynamically -->
<div class="form-group"> </select>
<label for="proxy-host">Host *</label>
<input type="text" id="proxy-host"
placeholder="proxy.example.com"
required>
</div>
<div class="form-group">
<label for="proxy-port">Port *</label>
<input type="number" id="proxy-port"
min="1" max="65535"
placeholder="8080"
required>
</div> </div>
</div> </div>
<div class="form-row"> <div id="domains-list" class="domains-list">
<!-- Dynamically populated -->
</div>
</section>
<section
id="domain-form-section"
class="domain-form-section hidden"
>
<h2 id="form-title">Add Domain</h2>
<form id="domain-form" class="domain-form">
<div class="form-group"> <div class="form-group">
<label for="proxy-username">Username (optional)</label> <label for="domain-input">Domain *</label>
<input type="text" id="proxy-username" <input
placeholder="username"> type="text"
id="domain-input"
placeholder="example.com"
pattern="^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$"
required
/>
<small class="form-hint"
>Enter domain without protocol (e.g.,
example.com)</small
>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="proxy-password">Password (optional)</label> <label for="proxy-type">Proxy Type *</label>
<div class="password-input"> <select id="proxy-type" required>
<input type="password" id="proxy-password" <option value="http">HTTP</option>
placeholder="••••••••"> <option value="socks">SOCKS5</option>
<button type="button" class="toggle-password" </select>
data-target="proxy-password"> </div>
👁
</button> <div class="form-group">
<label for="profile-select"
>Use Profile (optional)</label
>
<select id="profile-select">
<option value="">-- Select Profile --</option>
<!-- Profiles will be added dynamically -->
</select>
<small class="form-hint"
>Selecting a profile will fill the fields
below</small
>
</div>
<div class="form-row">
<div class="form-group">
<label for="proxy-host">Host *</label>
<input
type="text"
id="proxy-host"
placeholder="proxy.example.com"
required
/>
</div>
<div class="form-group">
<label for="proxy-port">Port *</label>
<input
type="number"
id="proxy-port"
min="1"
max="65535"
placeholder="8080"
required
/>
</div> </div>
</div> </div>
</div>
<div class="form-group"> <div class="form-row">
<label for="domain-tags">Tags (optional)</label> <div class="form-group">
<input type="text" id="domain-tags" <label for="proxy-username"
placeholder="work, personal, blocked"> >Username (optional)</label
<small class="form-hint">Comma-separated tags for organization</small> >
</div> <input
type="text"
<div class="form-group checkbox-group"> id="proxy-username"
<label class="checkbox-label"> placeholder="username"
<input type="checkbox" id="proxy-enabled" checked> />
<span>Enable proxy for this domain</span> </div>
</label> <div class="form-group">
</div> <label for="proxy-password"
>Password (optional)</label
<div class="form-actions"> >
<button type="button" id="cancel-form" class="btn btn-secondary"> <div class="password-input">
Cancel <input
</button> type="password"
<button type="submit" id="save-domain" class="btn btn-primary"> id="proxy-password"
Save Domain placeholder="••••••••"
</button> />
</div> <button
</form> type="button"
</section> class="toggle-password"
data-target="proxy-password"
<section id="profile-form-section" class="profile-form-section hidden"> >
<h2 id="profile-form-title">Add Profile</h2> 👁
</button>
<form id="profile-form" class="profile-form"> </div>
<div class="form-group">
<label for="profile-name-input">Profile Name *</label>
<input type="text" id="profile-name-input"
placeholder="My Proxy Profile"
required>
<small class="form-hint">Enter a unique name for this profile</small>
</div>
<div class="form-group">
<label for="profile-proxy-type">Proxy Type *</label>
<select id="profile-proxy-type" required>
<option value="http">HTTP</option>
<option value="socks">SOCKS5</option>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label for="profile-proxy-host">Host *</label>
<input type="text" id="profile-proxy-host"
placeholder="proxy.example.com"
required>
</div>
<div class="form-group">
<label for="profile-proxy-port">Port *</label>
<input type="number" id="profile-proxy-port"
min="1" max="65535"
placeholder="8080"
required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="profile-proxy-username">Username (optional)</label>
<input type="text" id="profile-proxy-username"
placeholder="username">
</div>
<div class="form-group">
<label for="profile-proxy-password">Password (optional)</label>
<div class="password-input">
<input type="password" id="profile-proxy-password"
placeholder="••••••••">
<button type="button" class="toggle-password"
data-target="profile-proxy-password">
👁
</button>
</div> </div>
</div> </div>
</div>
<div class="form-actions"> <div class="form-group">
<button type="button" id="cancel-profile-form" class="btn btn-secondary"> <label for="domain-tags">Tags (optional)</label>
Cancel <input
type="text"
id="domain-tags"
placeholder="work, personal, blocked"
/>
<small class="form-hint"
>Comma-separated tags for organization</small
>
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input
type="checkbox"
id="proxy-enabled"
checked
/>
<span>Enable proxy for this domain</span>
</label>
</div>
<div class="form-actions">
<button
type="button"
id="cancel-form"
class="btn btn-secondary"
>
Cancel
</button>
<button
type="submit"
id="save-domain"
class="btn btn-primary"
>
Save Domain
</button>
</div>
</form>
</section>
<section
id="profile-form-section"
class="profile-form-section hidden"
>
<h2 id="profile-form-title">Add Profile</h2>
<form id="profile-form" class="profile-form">
<div class="form-group">
<label for="profile-name-input"
>Profile Name *</label
>
<input
type="text"
id="profile-name-input"
placeholder="My Proxy Profile"
required
/>
<small class="form-hint"
>Enter a unique name for this profile</small
>
</div>
<div class="form-group">
<label for="profile-proxy-type">Proxy Type *</label>
<select id="profile-proxy-type" required>
<option value="http">HTTP</option>
<option value="socks">SOCKS5</option>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label for="profile-proxy-host">Host *</label>
<input
type="text"
id="profile-proxy-host"
placeholder="proxy.example.com"
required
/>
</div>
<div class="form-group">
<label for="profile-proxy-port">Port *</label>
<input
type="number"
id="profile-proxy-port"
min="1"
max="65535"
placeholder="8080"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="profile-proxy-username"
>Username (optional)</label
>
<input
type="text"
id="profile-proxy-username"
placeholder="username"
/>
</div>
<div class="form-group">
<label for="profile-proxy-password"
>Password (optional)</label
>
<div class="password-input">
<input
type="password"
id="profile-proxy-password"
placeholder="••••••••"
/>
<button
type="button"
class="toggle-password"
data-target="profile-proxy-password"
>
👁
</button>
</div>
</div>
</div>
<div class="form-actions">
<button
type="button"
id="cancel-profile-form"
class="btn btn-secondary"
>
Cancel
</button>
<button
type="submit"
id="save-profile"
class="btn btn-primary"
>
Save Profile
</button>
</div>
</form>
</section>
<section class="advanced-settings-section">
<h2>Advanced Settings</h2>
<div class="form-group settings-row">
<label for="theme-select">Theme</label>
<select id="theme-select">
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<small class="form-hint"
>Choose light, dark, or follow your device
theme.</small
>
</div>
</section>
<section class="management-section">
<h2>Management</h2>
<div class="management-buttons">
<button id="clear-domains" class="btn btn-danger">
Clear All Domains
</button> </button>
<button type="submit" id="save-profile" class="btn btn-primary"> <button id="clear-profiles" class="btn btn-danger">
Save Profile Clear All Profiles
</button>
<button id="clear-all" class="btn btn-danger">
Clear Everything
</button> </button>
</div> </div>
</form> </section>
</section> </main>
<section class="advanced-settings-section">
<h2>Advanced Settings</h2>
<div class="form-group settings-row">
<label for="theme-select">Theme</label>
<select id="theme-select">
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<small class="form-hint">Choose light, dark, or follow your device theme.</small>
</div>
</section>
<section class="management-section">
<h2>Management</h2>
<div class="management-buttons">
<button id="clear-domains" class="btn btn-danger">
Clear All Domains
</button>
<button id="clear-profiles" class="btn btn-danger">
Clear All Profiles
</button>
<button id="clear-all" class="btn btn-danger">
Clear Everything
</button>
</div>
</section>
</main>
<footer> <footer>
<div class="footer-content"> <div class="footer-content">
<p>Proxy Switcher v1.0.1</p> <p>Proxy Switcher v20261009.1</p>
</div> </div>
</footer> </footer>
</div> </div>
<script src="browser-api.js"></script> <script src="browser-api.js"></script>
<script src="options.js"></script> <script src="options.js"></script>
</body> </body>
</html> </html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB