Compare commits

..
12 Commits
Author SHA1 Message Date
wt 5cbada02c3 Merge pull request 'added screenshots and fix manifest.json' (#5) from develop into main
Build and publish extension / Build extension packages (push) Successful in 54s
Build and publish extension / Publish stores and attach release assets (push) Skipped
Build and publish extension / Publish stores and attach release assets (release) Canceled after 0s
Build and publish extension / Build extension packages (release) Canceled after 10s
Reviewed-on: #5
2026-10-09 17:48:13 +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 427a1875f4 Merge pull request 'updated LICENSE and fix ci/cd №3' (#4) from develop into main
Build and publish extension / Build extension packages (push) Successful in 54s
Build and publish extension / Publish stores and attach release assets (push) Skipped
Build and publish extension / Build extension packages (release) Successful in 2m46s
Build and publish extension / Publish stores and attach release assets (release) Canceled after 9m37s
Reviewed-on: #4
2026-10-09 17:32:39 +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 cd9d7c02cc Merge pull request 'fix ci/cd №2' (#3) from develop into main
Build and publish extension / Build extension packages (push) Successful in 50s
Build and publish extension / Publish stores and attach release assets (push) Skipped
Build and publish extension / Build extension packages (release) Successful in 51s
Build and publish extension / Publish stores and attach release assets (release) Failing after 40s
Reviewed-on: #3
2026-10-09 17:16:20 +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 2067b7f94c Merge pull request 'fix ci/cd' (#2) from develop into main
Build and publish extension / Build extension packages (push) Successful in 52s
Build and publish extension / Publish stores and attach release assets (push) Skipped
Build and publish extension / Build extension packages (release) Successful in 51s
Build and publish extension / Publish stores and attach release assets (release) Failing after 38s
Reviewed-on: #2
2026-10-09 17:03:29 +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 5dcd170d74 Merge pull request 'fix error if no secrets/credentials' (#1) from develop into main
Build and publish extension / Build extension packages (push) Successful in 52s
Build and publish extension / Publish stores and attach release assets (push) Skipped
Build and publish extension / Publish stores and attach release assets (release) Canceled after 0s
Build and publish extension / Build extension packages (release) Canceled after 33s
Reviewed-on: #1
2026-10-09 16:55:16 +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
11 changed files with 460 additions and 262 deletions

No files matched your search

+82 -33
View File
@@ -9,7 +9,7 @@ on:
workflow_dispatch:
inputs:
version:
description: "Version to validate (for example 1.0.1)"
description: "Version to validate (for example 20261009)"
required: true
type: string
@@ -48,7 +48,7 @@ jobs:
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+){1,3}", expected):
if not re.fullmatch(r"\d+(\.\d+){0,3}", expected):
raise SystemExit(f"Release version must be numeric, got {expected!r}")
PY
@@ -57,17 +57,24 @@ jobs:
- name: Prepare Firefox source
run: |
mkdir -p dist/firefox-source
rsync -a \
--exclude '.git/' \
--exclude '.github/' \
--exclude 'web-ext-artifacts/' \
--exclude 'manifest.chrome.json' \
--exclude 'background-chrome.js' \
--exclude 'background-service-worker.js' \
--exclude 'browser-api.js' \
--exclude 'scripts/' \
./ dist/firefox-source/
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: |
@@ -110,6 +117,26 @@ jobs:
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:
@@ -122,21 +149,30 @@ jobs:
path: dist/packages
- name: Set up Firefox signing tools
if: steps.credentials.outputs.firefox == 'true'
run: |
npm install --global web-ext
mkdir -p dist/firefox-source
rsync -a \
--exclude '.git/' \
--exclude '.github/' \
--exclude 'web-ext-artifacts/' \
--exclude 'manifest.chrome.json' \
--exclude 'background-chrome.js' \
--exclude 'background-service-worker.js' \
--exclude 'browser-api.js' \
--exclude 'scripts/' \
./ dist/firefox-source/
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; }
@@ -145,6 +181,7 @@ jobs:
--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
@@ -153,6 +190,7 @@ jobs:
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
@@ -177,20 +215,31 @@ jobs:
-H "Authorization: Bearer $token" \
"https://chromewebstore.googleapis.com/v2/publishers/$CWS_PUBLISHER_ID/items/$CWS_EXTENSION_ID:publish"
- name: Attach signed Firefox and Chrome packages to release
- 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 "${{ github.event.release.tag_name }}" \
dist/packages/proxyswitcherforkbywt-firefox-signed.xpi \
"$chrome_zip" \
gh release upload "$RELEASE_TAG" \
dist/release-assets/* \
--clobber
- name: Upload signed packages as workflow artifacts
- name: Upload release packages as workflow artifacts
uses: actions/upload-artifact@v4
with:
name: signed-extension-packages-${{ github.event.release.tag_name }}
name: release-extension-packages-${{ github.event.release.tag_name }}
if-no-files-found: error
path: |
dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
dist/packages/**/proxyswitcherforkbywt-chrome.zip
path: dist/release-assets/*
+2
View File
@@ -30,6 +30,8 @@ This repository contains the Firefox WebExtension **Proxy Switcher Fork By WT**.
- 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
+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.
+10 -2
View File
@@ -104,7 +104,11 @@ Configure these repository or environment secrets before publishing:
- `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.
The stores must already contain their initial listings and required privacy information. Store review still applies; the workflow submits the update and does not bypass review. Never commit any of these credentials.
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:
@@ -124,4 +128,8 @@ python -m json.tool manifest.json >/dev/null
## Project version
Current version: **1.0.1**
Current version: **20261009**
## 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,
"name": "Proxy Switcher Fork By WT",
"version": "1.0.1",
"version": "20261009",
"description": "Selectively apply proxy servers to specific domains.",
"minimum_chrome_version": "99",
"icons": {
+2 -2
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"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.",
"icons": {
"16": "icons/proxy-switcher-16.png",
@@ -35,7 +35,7 @@
},
"browser_specific_settings": {
"gecko": {
"id": "proxy-switcher-for-by-wt@9c0b2d3d",
"id": "proxy-switcher-fork-by-wt@9c0b2d3d",
"strict_min_version": "91.1.0"
},
"gecko_android": {
+326 -224
View File
@@ -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</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