Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b6f51a3e5 | ||
|
|
b8046e7b9a | ||
|
|
41456c5c0f | ||
|
|
dd50029bc5 | ||
|
|
4dd5f48e3b | ||
|
|
9e2ef513a0 | ||
|
|
319bcdefbd | ||
|
|
07a0f2c1e3 | ||
|
|
e88fb193df | ||
|
|
2f5568fb37 | ||
|
|
1926ac166b |
No files matched your search
@@ -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/*
|
||||
@@ -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.
|
||||
- `icons/` — extension icon sizes referenced by `manifest.json`.
|
||||
- `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
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
- 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`.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -48,7 +48,7 @@ The manifest requests these permissions:
|
||||
- `webRequest` and `webRequestBlocking` — provide proxy authentication credentials.
|
||||
- `<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
|
||||
|
||||
@@ -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.
|
||||
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
## 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:
|
||||
|
||||
```sh
|
||||
@@ -112,4 +128,8 @@ python -m json.tool manifest.json >/dev/null
|
||||
|
||||
## Project version
|
||||
|
||||
Current version: **1.0.1**
|
||||
Current version: **20261009.1**
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the [MIT License](LICENSE).
|
||||
@@ -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,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Proxy Switcher Fork By WT",
|
||||
"version": "1.0.1",
|
||||
"version": "20261009.1",
|
||||
"description": "Selectively apply proxy servers to specific domains.",
|
||||
"minimum_chrome_version": "99",
|
||||
"icons": {
|
||||
|
||||
+7
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"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.",
|
||||
"icons": {
|
||||
"16": "icons/proxy-switcher-16.png",
|
||||
@@ -35,11 +35,14 @@
|
||||
},
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "proxy-switcher-for-by-wt@9c0b2d3d",
|
||||
"strict_min_version": "91.1.0"
|
||||
"id": "proxy-switcher-fork-by-wt@9c0b2d3d",
|
||||
"strict_min_version": "140.0",
|
||||
"data_collection_permissions": {
|
||||
"required": ["none"]
|
||||
}
|
||||
},
|
||||
"gecko_android": {
|
||||
"strict_min_version": "113.0"
|
||||
"strict_min_version": "142.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
+326
-224
@@ -1,249 +1,351 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Proxy Switcher - Options</title>
|
||||
<link rel="stylesheet" href="options.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modal Overlay -->
|
||||
<div id="modal-overlay" class="modal-overlay hidden"></div>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Proxy Switcher - Options</title>
|
||||
<link rel="stylesheet" href="options.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modal Overlay -->
|
||||
<div id="modal-overlay" class="modal-overlay hidden"></div>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1><img src="icons/proxy-switcher-64.png" alt="Proxy Switcher"> Proxy Switcher</h1>
|
||||
<p class="subtitle">Manage domain-specific proxy settings</p>
|
||||
</header>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>
|
||||
<img
|
||||
src="icons/proxy-switcher-64.png"
|
||||
alt="Proxy Switcher"
|
||||
/>
|
||||
Proxy Switcher
|
||||
</h1>
|
||||
<p class="subtitle">Manage domain-specific proxy settings</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="profiles-section">
|
||||
<div class="section-header">
|
||||
<h2>Proxy Profiles</h2>
|
||||
<button id="add-profile" class="btn btn-primary">
|
||||
<span class="icon">+</span> Add Profile
|
||||
</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>
|
||||
<main>
|
||||
<section class="profiles-section">
|
||||
<div class="section-header">
|
||||
<h2>Proxy Profiles</h2>
|
||||
<button id="add-profile" class="btn btn-primary">
|
||||
<span class="icon">+</span> Add Profile
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="proxy-type">Proxy Type *</label>
|
||||
<select id="proxy-type" required>
|
||||
<option value="http">HTTP</option>
|
||||
<option value="socks">SOCKS5</option>
|
||||
</select>
|
||||
<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="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 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 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">
|
||||
<label for="proxy-username">Username (optional)</label>
|
||||
<input type="text" id="proxy-username"
|
||||
placeholder="username">
|
||||
<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 class="form-group">
|
||||
<label for="proxy-password">Password (optional)</label>
|
||||
<div class="password-input">
|
||||
<input type="password" id="proxy-password"
|
||||
placeholder="••••••••">
|
||||
<button type="button" class="toggle-password"
|
||||
data-target="proxy-password">
|
||||
👁
|
||||
</button>
|
||||
<label for="proxy-type">Proxy Type *</label>
|
||||
<select id="proxy-type" required>
|
||||
<option value="http">HTTP</option>
|
||||
<option value="socks">SOCKS5</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<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 class="form-group">
|
||||
<label for="domain-tags">Tags (optional)</label>
|
||||
<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 class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="proxy-username"
|
||||
>Username (optional)</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="proxy-username"
|
||||
placeholder="username"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="proxy-password"
|
||||
>Password (optional)</label
|
||||
>
|
||||
<div class="password-input">
|
||||
<input
|
||||
type="password"
|
||||
id="proxy-password"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-password"
|
||||
data-target="proxy-password"
|
||||
>
|
||||
👁
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" id="cancel-profile-form" class="btn btn-secondary">
|
||||
Cancel
|
||||
<div class="form-group">
|
||||
<label for="domain-tags">Tags (optional)</label>
|
||||
<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 type="submit" id="save-profile" class="btn btn-primary">
|
||||
Save Profile
|
||||
<button id="clear-profiles" class="btn btn-danger">
|
||||
Clear All Profiles
|
||||
</button>
|
||||
<button id="clear-all" class="btn btn-danger">
|
||||
Clear Everything
|
||||
</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 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>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<p>Proxy Switcher v1.0.1</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<p>Proxy Switcher v20261009.1</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="browser-api.js"></script>
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
<script src="browser-api.js"></script>
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</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 |
Reference in new issue
Block a user