Compare commits
14
Commits
d0c5fc5679
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cbada02c3 | ||
|
|
41456c5c0f | ||
|
|
427a1875f4 | ||
|
|
dd50029bc5 | ||
|
|
cd9d7c02cc | ||
|
|
4dd5f48e3b | ||
|
|
2067b7f94c | ||
|
|
9e2ef513a0 | ||
|
|
5dcd170d74 | ||
|
|
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)"
|
||||||
|
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,6 +16,7 @@ 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
|
||||||
|
|
||||||
@@ -28,6 +29,9 @@ This repository contains the Firefox WebExtension **Proxy Switcher Fork By WT**.
|
|||||||
- 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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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**
|
||||||
|
|
||||||
|
## 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,
|
"manifest_version": 3,
|
||||||
"name": "Proxy Switcher Fork By WT",
|
"name": "Proxy Switcher Fork By WT",
|
||||||
"version": "1.0.1",
|
"version": "20261009",
|
||||||
"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": {
|
||||||
|
|||||||
+2
-2
@@ -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",
|
||||||
"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,7 +35,7 @@
|
|||||||
},
|
},
|
||||||
"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": "91.1.0"
|
||||||
},
|
},
|
||||||
"gecko_android": {
|
"gecko_android": {
|
||||||
|
|||||||
+326
-224
@@ -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</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 |
Reference in new issue
Block a user