[
  {
    "path": ".gitattributes",
    "content": "*.js linguist-language=python\n*.css linguist-language=python\n*.html linguist-language=python\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.11\n\nWORKDIR /app\n\nCOPY requirements.txt .\n\nRUN pip install --upgrade pip -i https://pypi.mirrors.ustc.edu.cn/simple/ && \\\n    pip install --no-cache-dir -r requirements.txt -i https://pypi.mirrors.ustc.edu.cn/simple/\n\nCOPY . .\n\nRUN rm -f config/config.ini\n\nVOLUME [\"/app/config\"]\n\nCMD [\"python\", \"app.py\"]\n\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "ProxyCat-Manual/Investigation Manual.md",
    "content": "Q：出现不正常代理地址，无法正常使用代理\n\n![1](./Investigation%20Manual.assets/1.png)\n\nA：检查 API 地址所提供的代理格式是否正确，是否给自己出口IP加白了。\n\n\n\nQ：为什么运行 ProxyCat 之后我的IP仍然没有改变？\n\nA：本项目并非全局代理，需要指向本地监听端口，并且保证代理服务器可用，请逐步排查自己的网络。\n\n\n\nQ：为什么端口扫描等情况下无法正常代理？\nA：端口扫描等任务并非发起HTTP类型等数据包，本工具旨在解决 Web 方面，并不支持其他协议。\n\n\n\nQ：为什么我遇到的问题没有在《报错手册》中查到？\n\nA：因为本手册是逐步完善，遇到了就添加，可以联系作者（提问先发50，如果问题无法通过百度或GPT查到解决方案，全额退还。）\n\n\n\nQ：出现报错内容 - ERROR - XXXXXXXXXXX\n\nA：将报错内容复制下来到百度上查询。\n\n\n\n"
  },
  {
    "path": "ProxyCat-Manual/Operation Manual.md",
    "content": "# ProxyCat 使用手册\n\n## 重要事项\n\n- Python版本最好为Python 3.11\n- Releases中为较为稳定的打包版本，不一定是最新\n- API 接口所获取的代理地址必须为 IP:PORT 格式且只提供一条地址\n\n## 源码使用及 Docker 部署\n\n### 源码手册\n\n**Windows&Mac**：浏览器访问位于 Github 的源码存储库并下载：[ProxyCat](https://github.com/honmashironeko/ProxyCat)\n\n![Windows Download](./Operation%20Manual.assets/Windows%20Download.png)\n\n**Linux**：通过 Git 方法拉取项目到本地\n\n```\ngit clone https://github.com/honmashironeko/ProxyCat.git\n```\n\n![Linux Download](./Operation%20Manual.assets/Linux%20Download.png)\n\n安装 Python 依赖（**请尽量保证Python版本为3.8-3.11**）\n\n```\npip install -r requirements.txt\n# 或使用国内源：\npip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/\n```\n\n进入 config 文件夹内找到 config.ini 配置文件，按照自己拥有的资源选择不同的代理服务器获取方法\n\n1️⃣如果您的代理服务器地址为固定的连接，不需要动态更换，可以使用本地 ip.txt 的方式提供格式如下所示\n\n```\n# 支持 http/https/socks5 三种代理服务器地址，支持账号密码校验\nsocks5://neko:123456@127.0.0.1:7890\nhttps://neko:123456@127.0.0.1:7890\nhttp://neko:123456@127.0.0.1:7890\nsocks5://127.0.0.1:7890\nhttps://127.0.0.1:7890\nhttp://127.0.0.1:7890\n...\n```\n\n2️⃣如果您是通过 API 方式获取代理地址，可以在 config.ini 中修改配置（配置后不再读取 ip.txt）\n\n```\n# config.ini 配置文件中将以下两条配置进行修改\nuse_getip = True\ngetip_url = 获取代理地址的 API 接口\n# 请注意，API 接口所获取的代理地址必须为 IP:PORT 格式且只提供一条地址，如果格式不同请到 getip.py 脚本中修改代码，如果您需要指定协议（默认为socks5）可以进入 getip.py 脚本中修改\n```\n\n当您配置完成之后就可以运行工具了\n\n```\npython ProxyCat.py\npython app.py (Web控制管理-推荐方式)\n```\n\n![Run](./Operation%20Manual.assets/Run.png)\n\n### Docker 手册\n\nWindows 可以下载 Docker 官方工具：[Docker Desktop](docs.dockerd.com.cn)\n\n![Docker%20Desktop%20Download](./Operation%20Manual.assets/Docker%20Desktop%20Download.png)\n\nLinux 可以通过清华大学源提供的脚本一键安装：[清华大学安装脚本](https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/)\n\n![Docker%20Download](./Operation%20Manual.assets/Docker%20Download.png)\n\n安装完成后请测试docker、docker-compose是否安装成功，如果安装失败请百度\n\nWindows&Linux 进入 ProxyCat 文件夹下（**在此之前请根据源码手册中 config.ini 配置部分完成参数修改**）运行以下命令进行部署\n\n```\n# 进入ProxyCat文件夹中并构建镜像和启动容器\ndocker-compose up -d --build\n\n# 停止服务和启动服务（每次修改完配置后需要重启服务）\ndocker-compose down | docker-compose up -d\n\n# 查看日志信息\ndocker logs proxycat\n\n# docker端口默认为1080和5000,1080为监听端口，5000为web页面管理如需其他端口请对应修改并放行\n```\n\n### 配置文件介绍\n\n```\n# 日志显示级别(默认为:1)\n# 0: 仅显示代理切换和错误信息\n# 1: 显示代理切换、倒计时和错误信息\n# 2: 显示所有详细信息\n# 仅终端管理时生效\ndisplay_level = 1\n\n# 本地服务器监听端口(默认为:1080)\n# Local server listening port (default:1080)\nport = 1080\n\n# Web 管理页面端口(默认为:5000)\nweb_port = 5000\n\n# 代理地址轮换模式：cycle 表示循环使用，loadbalance 表示负载均衡(默认为:cycle)\n# Proxy rotation mode: cycle means cyclic use, loadbalance means load balancing (default:cycle)\nmode = cycle\n\n# 代理地址更换时间（秒），设置为 0 时每次请求都更换 IP(默认为:300)\n# Proxy address rotation interval (seconds), when set to 0, IP changes with each request (default:300)\ninterval = 300\n\n# 是否使用 getip 模块获取代理地址 True or False(默认为:False)\n# Whether to use getip module to obtain proxy addresses True or False (default:False)\nuse_getip = False\n\n# 获取新代理地址的URL\n# URL to get new proxy address\ngetip_url = http://example.com/getip\n\n# 代理服务器认证用户名(如果代理服务器需要认证)\n# Proxy server authentication username (if proxy server requires authentication)\nproxy_username = \n\n# 代理服务器认证密码(如果代理服务器需要认证)\n# Proxy server authentication password (if proxy server requires authentication)\nproxy_password = \n\n# 代理地址列表文件(默认为:ip.txt)\n# Proxy address list file (default:ip.txt)\nproxy_file = ip.txt\n\n# 是否启用代理检测功能 True or False(默认为True)\n# Whether to enable proxy detection feature True or False (default:True)\ncheck_proxies = True\n\n# 语言设置 (cn/en)\n# Language setting (cn/en)\nlanguage = cn\n\n# IP白名单文件路径（留空则不启用白名单）\n# IP whitelist file path (leave empty to disable whitelist)\nwhitelist_file = whitelist.txt\n\n# IP黑名单文件路径（留空则不启用黑名单）\n# IP blacklist file path (leave empty to disable blacklist)\nblacklist_file = blacklist.txt\n\n# IP认证优先级（whitelist/blacklist）\n# IP authentication priority (whitelist/blacklist)\n# whitelist: 优先判断白名单，在白名单中的IP直接放行\n# whitelist: prioritize whitelist check, IPs in whitelist are allowed directly\n# blacklist: 优先判断黑名单，在黑名单中的IP直接拒绝\n# blacklist: prioritize blacklist check, IPs in blacklist are rejected directly\nip_auth_priority = whitelist\n\n# Web 管理页面访问token，留空则无需token(默认为:honmashironeko)\ntoken = honmashironeko\n\n# 在[Users]下面是用户管理组，\"账号=密码\"一行一个，留空时代理无需身份鉴别(默认为:neko=123456)\n[Users]\nneko=123456\n```\n\n### Web 控制面板\n\n采用源码部署的话通过 **python app.py** 启动 Web 控制面板，并根据提示访问 Web\n\n![Clip_2025-02-21_16-23-35](./Operation%20Manual.assets/Clip_2025-02-21_16-23-35.png)\n\n![Clip_2025-03-03_10-54-38](./Operation%20Manual.assets/Clip_2025-03-03_10-54-38.png)\n\n![Clip_2025-03-03_10-55-05](./Operation%20Manual.assets/Clip_2025-03-03_10-55-05.png)\n\n![Clip_2025-03-03_10-55-37](./Operation%20Manual.assets/Clip_2025-03-03_10-55-37.png)\n\n## 问题Q&A\n\nQ：为什么运行后我的XXX工具代理还是没换？\n\nA：ProxyCat 并不是全局代理工具，需要XXX工具支持使用代理，将流量发送到 ProxyCat 的本地监听端口才会经过代理池。\n\n\n\nQ：为什么倒计时结束后代理没有更换？\n\nA：ProxyCat 为了节约硬件资源和代理服务器资源，特意修改运行逻辑为有流量经过的时候才会更换代理，这样可以减少资源的浪费，同时可以部署一次，长期可用。\n\n\n\nQ：为什么我用 getip 方式获取代理地址的时候，首次运行会报 None ，没有可用的代理地址？\n\nA：为了防止资源浪费，通过 getip 获取的情况一般是付费购买静态短效IP，运行就获取的话会浪费大量资源从而导致资金损耗，为避免这种情况发生，首次运行不会主动获取，您只需要正常使用发包，ProxyCat 会自动获取并发送。\n\n\n\nQ：getip.py 当中的appKey和anquanma是做什么的？\n\nA：这两个参数是用作自动将当前请求IP添加到服务商（请查看readme中最下面的第一个推荐）的白名单中，免去每次IP变更需重新添加的烦恼，其中anquanma(安全码)需要到个人中心配置。\n\n\n\nQ：我自己有静态IP提供地址该怎么用？\nA：将地址填入getip_url，如果有账号密码请写在 proxy_username 、proxy_password 。\n\n\n\nQ：为什么我会遇到 XXX 报错？为什么不能用？\n\nA：可先看[《排查手册》](https://github.com/honmashironeko/ProxyCat/blob/main/ProxyCat-Manual/Investigation%20Manual.md)，无法修复的情况下可以找作者询问，提问前请先支付50元作为时间的购买费用，如果您的问题属于百度可查或手册中有的，费用将不会返还；如果属于工具BUG或功能建议，费用将全额返还并将您列入本项目的感谢名单中。（实在是太多人多在问一些非常简单且写在帮助中的问题，时间被极大的浪费了，同时有很多态度非常恶劣的人，这不是我所想要的） \n"
  },
  {
    "path": "ProxyCat-Manual/logs.md",
    "content": "### 2025/03/23\n\n- 修复负载均衡模式无法调用的BUG\n- 修复socks5连接错误\n- 修复http、socks5监听下的目标网站错误和代理地址失效情况一致导致无法正常触发代理切换\n- 修改代理有效性校验，配置为可控检测，关闭后将不会进行有效性检查避免特殊情况一直切换\n- 修复并发下导致大规模触发更换和提示的问题，锁定操作的原子性\n- 修复大量细节逻辑、描述错误\n- 当前代理切换触发条件为：时间间隔到期切换、代理失效自动切换、Web手动切换、API下首次请求自动获取\n\n###  2025/03/17\n\n- 修复目标站点本身错误时会触发代理切换的错误逻辑\n- 修改连接关闭方式\n- 优化监听服务器性能\n- 修复多处错误BUG\n\n### 2025/03/14\n\n- 修复'_last_used'报错问题，连接关闭方式修正\n- 修复本地代理读取时切换逻辑失效问题\n- 增加切换时间间隔设置为0时进入每次请求都更换IP\n\n### 2025/03/03\n\n- 美化 Web管理界面\n- 修复大量 BUG\n- 添加更多处理小脚本辅助使用\n\n### 2025/02/21\n\n- 增加 Web 管理界面\n\n- 增加多用户模式\n\n- 代码结构大改\n\n- config.in及相关文件动态更新不需要重启\n\n- 增加日志显示级别控制\n- 增加记录连接人信息日志，包括连接的IP和使用的账号密码\n- 以及其他乱七八糟的修改，这次大版本更新改的太多，我有点忘记了~\n\n### 2025/02/06\n\n- Docker 安装依赖库采用国内源\n- 增加Getip方式下主动提示：当前为API模式，收到请求将自动获取代理地址\n- 修改加白方式，自动根据请求结果加白\n\n### 2025/01/14\n\n- 增加 getip 方式下自动添加白名单机制。\n- 支持带有账号密码的本地读取、getip获取、有效性校验的代理地址。、\n- 整理代码结构，合并一些代码、删除一些多余代码。\n\n### 2025/01/07\n\n- 引入连接池机制提高性能。\n- 优化部分错误处理和日志记录。 \n- 代理切换机制优化。\n\n### 2025/01/03\n\n- 集中配置参数到配置文件中管理，提升维护便利。\n- 修复部分已知BUG，并提升稳定性和并发能力。\n\n### 2025/01/02\n\n- 重构软件结构，更加整洁易用。\n- 新增支持黑白名单机制进行身份认证。\n- 在使用GetIP方式的时候，需要先收到一次请求才会获取代理，防止每次运行都浪费资金。\n- 语言配置逻辑更改，不再分为两个版本，通过config.ini文件中的语言配置参数进行显示。\n- 配置信息面板更新，不配置账号密码的情况下也能直接复制地址使用。\n- 新增docker方式部署。\n\n### **2024/10/23**\n\n- 重构代码结构，将部分代码分割成单独文件。\n- 支持代理过程中，遇到代理服务器突然失效，自动请求更换新的代理服务器，并重置更换计时器。\n\n### 2024/09/29\n\n- 去除使用较少的单次循环，更换为自定义模式，可根据需求自定义更换代理的逻辑。\n- 对代理有效性检测修改为异步，提高速度。\n- 去除问题较多的 SOCKS4 协议的代理支持。\n- 对日志系统进行美化。\n- 改进异常处理逻辑。\n- 增加对代理格式的校验，确保格式正确。\n\n### 2024/09/10\n\n- 优化并发效率，支持在未收到响应包的情况下提前进行下一个请求，提高效率。\n- 增加负载均衡模式，该模式下将随机向代理地址发送请求，并利用并发代理的方式，提高请求效率。\n- 代理有效性检测修改为异步，提高效率。\n\n### 2024/09/09\n\n- 增加功能，可设置首次启动时是否对 `ip.txt` 中的代理地址进行有效性校验，并只使用有效的代理地址。\n- 函数降级，支持更低版本的 Python。\n\n### 2024/09/03\n\n- 增加本地 SOCKS5 监听，适配更多软件。\n- 部分函数更换，适配更低版本的 Python。\n- 美化回显内容。\n\n### 2024/08/31\n\n- 项目大结构调整。\n- 美化显示，持续提示下一次更换代理地址的时间。\n- 支持 `Ctrl+C` 停止运行。\n- 大幅度调整为异步请求，并发效率提升，实测 **1000** 并发，共 **5000** 包，丢包约 **50** 包，稳定性约 **99%**，**500** 并发无丢包。\n- 不再采取运行时指定参数方案，修改为从本地 `ini` 配置文件中读取，易用性更高。\n- 支持本地无认证，适配更多软件代理方式。\n- 增加版本检测功能，自动提示版本信息。\n- 增加代理服务器地址的身份鉴别功能，仅支持本地读取，因大多数 API 需白名单，未提供重复。\n- 增加功能，仅在收到新请求的情况下才使用 `getip` 更新，减少 IP 消耗。\n- 增加自识别代理服务器地址协议，以适配更多代理商。\n- 增加支持 HTTPS、SOCKS4 代理协议，目前已覆盖 HTTP、HTTPS、SOCKS5、SOCKS4 协议。\n- 修改 `asyncio.timeout()` 为 `asyncio.wait_for()`，适配更低的 Python 版本。\n\n### 2024/08/25\n\n- 读取 `ip.txt` 时自动跳过空行。\n- 将 `httpx` 更换为并发池，提高性能。\n- 增加缓冲字典，相同站点降低延迟。\n- 每次请求更换 IP 逻辑修改为随机选择代理。\n- 采用更高效的结构和算法，优化请求处理逻辑。\n\n### 2024/08/24\n\n- 采用异步方案提高并发能力和减少超时。\n- 重复代码封装，提高代码复用性。\n\n### 2024/08/23\n\n- 修改并发逻辑。\n- 增加身份鉴别功能。\n- 增加 IP 获取接口，永久更换 IP。\n- 增加每次请求更换 IP 功能。"
  },
  {
    "path": "ProxyCat.py",
    "content": "from wsgiref import headers\nfrom modules.modules import ColoredFormatter, load_config, DEFAULT_CONFIG, check_proxies, check_for_updates, get_message, load_ip_list, print_banner, logos\nimport threading, argparse, logging, asyncio, time, socket, signal, sys, os\nfrom concurrent.futures import ThreadPoolExecutor\nfrom modules.proxyserver import AsyncProxyServer\nfrom colorama import init, Fore, Style\nfrom itertools import cycle\nfrom tqdm import tqdm\nimport base64\nfrom configparser import ConfigParser\n\ninit(autoreset=True)\n\ndef setup_logging():\n    log_format = '%(asctime)s - %(levelname)s - %(message)s'\n    formatter = ColoredFormatter(log_format)\n    console_handler = logging.StreamHandler()\n    console_handler.setFormatter(formatter)\n    logging.basicConfig(level=logging.INFO, handlers=[console_handler])\n\ndef update_status(server):\n    def print_proxy_info():\n        status = f\"{get_message('current_proxy', server.language)}: {server.current_proxy}\"\n        logging.info(status)\n\n    def reload_server_config(new_config):\n        old_use_getip = server.use_getip\n        old_mode = server.mode\n        old_port = int(server.config.get('port', '1080'))\n        \n        server.config.update(new_config)\n        server._update_config_values(new_config)\n        \n        if old_use_getip != server.use_getip or old_mode != server.mode:\n            server._handle_mode_change()\n        \n        if old_port != server.port:\n            logging.info(get_message('port_changed', server.language, old_port, server.port))\n\n    config_file = 'config/config.ini'\n    ip_file = server.proxy_file\n    last_config_modified_time = os.path.getmtime(config_file) if os.path.exists(config_file) else 0\n    last_ip_modified_time = os.path.getmtime(ip_file) if os.path.exists(ip_file) else 0\n    display_level = int(server.config.get('display_level', '1'))\n    is_docker = os.path.exists('/.dockerenv')\n    \n    while True:\n        try:\n            if os.path.exists(config_file):\n                current_config_modified_time = os.path.getmtime(config_file)\n                if current_config_modified_time > last_config_modified_time:\n                    logging.info(get_message('config_file_changed', server.language))\n                    new_config = load_config(config_file)\n                    reload_server_config(new_config)\n                    last_config_modified_time = current_config_modified_time\n                    continue\n            \n            if os.path.exists(ip_file) and not server.use_getip:\n                current_ip_modified_time = os.path.getmtime(ip_file)\n                if current_ip_modified_time > last_ip_modified_time:\n                    logging.info(get_message('proxy_file_changed', server.language))\n                    server._reload_proxies()\n                    last_ip_modified_time = current_ip_modified_time\n                    continue\n\n            if display_level == 0:\n                if not hasattr(server, 'last_proxy') or server.last_proxy != server.current_proxy:\n                    print_proxy_info()\n                    server.last_proxy = server.current_proxy\n                time.sleep(1)\n                continue\n\n            if server.mode == 'loadbalance':\n                if display_level >= 1:\n                    print_proxy_info()\n                time.sleep(5)\n                continue\n\n            time_left = server.time_until_next_switch()\n            if time_left == float('inf'):\n                if display_level >= 1:\n                    print_proxy_info()\n                time.sleep(5)\n                continue\n            \n            if not hasattr(server, 'last_proxy') or server.last_proxy != server.current_proxy:\n                print_proxy_info()\n                server.last_proxy = server.current_proxy\n                server.previous_proxy = server.current_proxy\n\n            total_time = int(server.interval)\n            elapsed_time = total_time - int(time_left)\n            \n            if display_level >= 1:\n                if elapsed_time > total_time:\n                    if hasattr(server, 'progress_bar'):\n                        if not is_docker:\n                            server.progress_bar.n = total_time\n                            server.progress_bar.refresh()\n                            server.progress_bar.close()\n                        delattr(server, 'progress_bar')\n                    if hasattr(server, 'last_update_time'):\n                        delattr(server, 'last_update_time')\n                    time.sleep(0.5)\n                    continue\n                \n                if is_docker:\n                    if not hasattr(server, 'last_update_time') or \\\n                       (time.time() - server.last_update_time >= (5 if display_level == 1 else 1) and elapsed_time <= total_time):\n                        if display_level >= 2:\n                            logging.info(f\"{get_message('next_switch', server.language)}: {time_left:.0f} {get_message('seconds', server.language)} ({elapsed_time}/{total_time})\")\n                        else:\n                            logging.info(f\"{get_message('next_switch', server.language)}: {time_left:.0f} {get_message('seconds', server.language)}\")\n                        server.last_update_time = time.time()\n                else:\n                    if not hasattr(server, 'progress_bar'):\n                        server.progress_bar = tqdm(\n                            total=total_time,\n                            desc=f\"{Fore.YELLOW}{get_message('next_switch', server.language)}{Style.RESET_ALL}\",\n                            bar_format='{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} ' + get_message('seconds', server.language),\n                            colour='green'\n                        )\n                    \n                    server.progress_bar.n = min(elapsed_time, total_time)\n                    server.progress_bar.refresh()\n                \n        except Exception as e:\n            if display_level >= 2:\n                logging.error(f\"Status update error: {e}\")\n            elif display_level >= 1:\n                logging.error(get_message('status_update_error', server.language))\n        time.sleep(1)\n\nasync def handle_client_wrapper(server, reader, writer, clients):\n    task = asyncio.create_task(server.handle_client(reader, writer))\n    clients.add(task)\n    try:\n        await task\n    except Exception as e:\n        logging.error(get_message('client_handle_error', server.language, e))\n    finally:\n        clients.remove(task)\n\nasync def run_server(server):\n    try:\n        await server.start()\n    except asyncio.CancelledError:\n        logging.info(get_message('server_closing', server.language))\n    except Exception as e:\n        if not server.stop_server:\n            logging.error(f\"Server error: {e}\")\n    finally:\n        await server.stop()\n\nasync def run_proxy_check(server):\n    if server.config.get('check_proxies', 'False').lower() == 'true':\n        logging.info(get_message('proxy_check_start', server.language))\n        valid_proxies = await check_proxies(server.proxies, server.test_url)\n        if valid_proxies:\n            server.proxies = valid_proxies\n            server.proxy_cycle = cycle(valid_proxies)\n            server.current_proxy = next(server.proxy_cycle)\n            logging.info(get_message('valid_proxies', server.language, valid_proxies))\n        else:\n            logging.error(get_message('no_valid_proxies', server.language))\n    else:\n        logging.info(get_message('proxy_check_disabled', server.language))\n\nclass ProxyCat:\n    def __init__(self):\n        cpu_count = os.cpu_count() or 1\n        self.executor = ThreadPoolExecutor(\n            max_workers=min(32, cpu_count + 4),\n            thread_name_prefix=\"proxy_worker\",\n            thread_name_format=\"proxy_worker_%d\"\n        )\n\n        loop = asyncio.get_event_loop()\n        loop.set_default_executor(self.executor)\n        \n        if hasattr(loop, 'set_task_factory'):\n            loop.set_task_factory(None)\n        \n        socket.setdefaulttimeout(30)\n        if hasattr(socket, 'TCP_NODELAY'):\n            socket.TCP_NODELAY = True\n        if hasattr(socket, 'SO_KEEPALIVE'):\n            socket.SO_KEEPALIVE = True\n        \n        if hasattr(socket, 'SO_REUSEADDR'):\n            socket.SO_REUSEADDR = True\n        if hasattr(socket, 'SO_REUSEPORT') and os.name != 'nt':\n            socket.SO_REUSEPORT = True\n        \n        if os.name != 'nt':\n            import resource\n            soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)\n            try:\n                resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard))\n            except ValueError:\n                resource.setrlimit(resource.RLIMIT_NOFILE, (soft, soft))\n            \n            soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)\n            try:\n                resource.setrlimit(resource.RLIMIT_NPROC, (hard, hard))\n            except ValueError:\n                pass\n\n        self.running = True\n        self.tasks = set()\n        self.max_tasks = 20000\n        self.task_semaphore = asyncio.Semaphore(self.max_tasks)\n        \n        signal.signal(signal.SIGINT, self.handle_shutdown)\n        signal.signal(signal.SIGTERM, self.handle_shutdown)\n        self.config = load_config('config/config.ini')\n        self.language = self.config.get('language', 'cn').lower()\n        \n        self.users = {}\n        config = ConfigParser()\n        config.read('config/config.ini', encoding='utf-8')\n        if config.has_section('Users'):\n            self.users = dict(config.items('Users'))\n        self.auth_required = bool(self.users)\n        \n        self.proxy_pool = {}\n        self.max_connections = 1000\n        self.connection_timeout = 30\n        self.read_timeout = 60\n\n    async def start_server(self):\n        try:\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            if hasattr(socket, 'SO_REUSEPORT') and os.name != 'nt':\n                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)\n            sock.bind((self.config.get('SERVER', 'host'), int(self.config.get('SERVER', 'port'))))\n            \n            server = await asyncio.start_server(\n                self.handle_client,\n                sock=sock\n            )\n            \n            logging.info(get_message('server_running', self.language,\n                self.config.get('SERVER', 'host'),\n                self.config.get('SERVER', 'port')))\n            \n            async with server:\n                await server.serve_forever()\n        except Exception as e:\n            logging.error(get_message('server_start_error', self.language, e))\n            sys.exit(1)\n\n    def handle_shutdown(self, signum, frame):\n        logging.info(get_message('server_shutting_down', self.language))\n        self.running = False\n        self.executor.shutdown(wait=True)\n        sys.exit(0)\n\n    async def handle_client(self, reader, writer):\n        task = asyncio.current_task()\n        self.tasks.add(task)\n        try:\n            if self.auth_required:\n                auth_header = headers.get('proxy-authorization')\n                if not auth_header or not self._authenticate(auth_header):\n                    writer.write(b'HTTP/1.1 407 Proxy Authentication Required\\r\\nProxy-Authenticate: Basic realm=\"Proxy\"\\r\\n\\r\\n')\n                    await writer.drain()\n                    return\n            \n            await asyncio.get_event_loop().run_in_executor(\n                self.executor,\n                self._handle_proxy_request,\n                reader,\n                writer\n            )\n        except Exception as e:\n            logging.error(get_message('client_process_error', self.language, e))\n        finally:\n            try:\n                writer.close()\n                await writer.wait_closed()\n            except:\n                pass\n            self.tasks.remove(task)\n\n    def _authenticate(self, auth_header):\n        if not self.users:\n            return True\n        \n        try:\n            scheme, credentials = auth_header.split()\n            if scheme.lower() != 'basic':\n                return False\n            \n            decoded_auth = base64.b64decode(credentials).decode()\n            username, password = decoded_auth.split(':')\n            \n            return username in self.users and self.users[username] == password\n        except:\n            return False\n\n    async def _handle_proxy_request(self, reader, writer):\n        try:\n            request_line = await reader.readline()\n            if not request_line:\n                return\n                \n            method, target, version = request_line.decode().strip().split(' ')\n            \n            if method == 'CONNECT':\n                await self._handle_connect(target, reader, writer)\n            else:\n                await self._handle_http(method, target, version, reader, writer)\n                \n        except Exception as e:\n            logging.error(get_message('request_handling_error', self.language, e))\n            try:\n                writer.close()\n            except:\n                pass\n\n    async def _handle_connect(self, target, reader, writer):\n        host, port = target.split(':')\n        port = int(port)\n        \n        try:\n            remote_reader, remote_writer = await asyncio.wait_for(\n                asyncio.open_connection(host, port),\n                timeout=self.connection_timeout\n            )\n            \n            writer.write(b'HTTP/1.1 200 Connection Established\\r\\n\\r\\n')\n            await writer.drain()\n            \n            await self._create_pipe(reader, writer, remote_reader, remote_writer)\n            \n        except Exception as e:\n            logging.error(get_message('proxy_forward_error', self.language, e))\n            writer.write(b'HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n            await writer.drain()\n\n    async def _handle_http(self, method, target, version, reader, writer):\n        try:\n            from urllib.parse import urlparse\n            url = urlparse(target)\n            host = url.hostname\n            port = url.port or 80\n            \n            remote_reader, remote_writer = await asyncio.wait_for(\n                asyncio.open_connection(host, port),\n                timeout=self.connection_timeout\n            )\n            \n            path = url.path + ('?' + url.query if url.query else '')\n            request = f'{method} {path} {version}\\r\\n'\n            remote_writer.write(request.encode())\n            \n            while True:\n                line = await reader.readline()\n                if line == b'\\r\\n':\n                    break\n                remote_writer.write(line)\n            remote_writer.write(b'\\r\\n')\n            \n            await self._create_pipe(reader, writer, remote_reader, remote_writer)\n            \n        except Exception as e:\n            logging.error(get_message('proxy_forward_error', self.language, e))\n            writer.write(b'HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n            await writer.drain()\n\n    async def _create_pipe(self, client_reader, client_writer, remote_reader, remote_writer):\n        try:\n            pipe1 = asyncio.create_task(self._pipe(client_reader, remote_writer))\n            pipe2 = asyncio.create_task(self._pipe(remote_reader, client_writer))\n            \n            done, pending = await asyncio.wait(\n                [pipe1, pipe2],\n                return_when=asyncio.FIRST_COMPLETED\n            )\n            \n            for task in pending:\n                task.cancel()\n                \n        except Exception as e:\n            logging.error(get_message('data_transfer_error', self.language, e))\n        finally:\n            try:\n                remote_writer.close()\n                await remote_writer.wait_closed()\n            except:\n                pass\n\n    async def _pipe(self, reader, writer):\n        try:\n            while True:\n                data = await reader.read(8192)\n                if not data:\n                    break\n                    \n                writer.write(data)\n                await writer.drain()\n                \n        except asyncio.CancelledError:\n            pass\n        except Exception as e:\n            logging.error(get_message('data_transfer_error', self.language, e))\n\n    def monitor_resources(self):\n        import psutil\n        process = psutil.Process(os.getpid())\n        \n        while self.running:\n            mem_info = process.memory_info()\n            logging.debug(f\"Memory usage: {mem_info.rss / 1024 / 1024:.2f} MB, \"\n                         f\"Connections: {len(self.tasks)}\")\n            time.sleep(60)\n\nif __name__ == '__main__':\n    setup_logging()\n    parser = argparse.ArgumentParser(description=logos())\n    parser.add_argument('-c', '--config', default='config/config.ini', help='配置文件路径')\n    args = parser.parse_args()\n    config = load_config(args.config)\n    server = AsyncProxyServer(config)\n    print_banner(config)\n    asyncio.run(check_for_updates(config.get('language', 'cn').lower()))\n    if not config.get('use_getip', 'False').lower() == 'true':\n        asyncio.run(run_proxy_check(server))\n    else:\n        logging.info(get_message('api_mode_notice', server.language))\n    \n    status_thread = threading.Thread(target=update_status, args=(server,), daemon=True)\n    status_thread.start()\n    \n    cleanup_thread = threading.Thread(target=lambda: asyncio.run(server.cleanup_clients()), daemon=True)\n    cleanup_thread.start()\n    \n    try:\n        asyncio.run(run_server(server))\n    except KeyboardInterrupt:\n        logging.info(get_message('user_interrupt', server.language))\n"
  },
  {
    "path": "README-EN.md",
    "content": "![ProxyCat](https://socialify.git.ci/honmashironeko/ProxyCat/image?description=1&descriptionEditable=A%20lightweight%20and%20excellent%20proxy%20pool%20middleware%20that%20implements%20automatic%20proxy%20rotation&font=Bitter&forks=1&issues=1&language=1&logo=https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F139044047%3Fv%3D4&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Dark)\n\n<p align=\"center\">\n  <a href=\"/README-EN.md\">English</a>\n  ·\n  <a href=\"/README.md\">简体中文</a>\n</p>\n\n## Table of Contents\n\n- [Development Background](#development-background)\n- [Features](#features)\n- [Installation and Usage](#installation-and-usage)\n- [Disclaimer](#disclaimer)\n- [Changelog](#changelog)\n- [Development Plan](#development-plan)\n- [Special Thanks](#special-thanks)\n- [Sponsor](#sponsor)\n- [Proxy Recommendations](#proxy-recommendations)\n\n## Development Background\n\nDuring penetration testing, it's often necessary to hide or change IP addresses to bypass security devices. However, tunnel proxies in the market are expensive, typically costing $3-6 per day, which is unaffordable for many. The author noticed that short-term IPs offer high cost-effectiveness, with each IP costing just a few cents, averaging $0.03-0.4 per day.\n\nTherefore, **ProxyCat** was born! This tool aims to transform short-term IPs (lasting from 1 to 60 minutes) into fixed IPs for other tools to use, creating a proxy pool server that can be used permanently after one deployment.\n\n![Project Principle](./assets/项目原理图.png)\n\n## Features\n\n- **Dual Protocol Listening**: Supports HTTP/SOCKS5 protocol listening, compatible with more tools.\n- **Triple Proxy Types**: Supports HTTP/HTTPS/SOCKS5 proxy servers with authentication.\n- **Flexible Switching Modes**: Supports sequential, random, and custom proxy selection for optimized traffic distribution.\n- **Dynamic Proxy Acquisition**: Get available proxies in real-time through GetIP function, supports API interface calls.\n- **Proxy Protection**: When using GetIP method, proxies are only fetched upon receiving requests, not at initial startup.\n- **Automatic Proxy Detection**: Automatically checks proxy validity at startup, removing invalid ones.\n- **Smart Proxy Switching**: Only obtains new proxies during request execution, reducing resource consumption.\n- **Invalid Proxy Handling**: Automatically validates and switches to new proxies when current ones fail.\n- **Authentication Support**: Supports username/password authentication and IP blacklist/whitelist management.\n- **Real-time Status Display**: Shows proxy status and switching times for dynamic monitoring.\n- **Dynamic Configuration**: Updates configuration without service restart.\n- **Web UI Interface**: Provides web management interface for convenient operation.\n- **Docker Deployment**: One-click Docker deployment with unified web management.\n- **Bilingual Support**: Supports Chinese and English language switching.\n- **Flexible Configuration**: Customize ports, modes, and authentication through config.ini.\n- **Version Check**: Automatic software update checking.\n\n## Tool Usage\n\n[ProxyCat Operation Manual](../main/ProxyCat-Manual/Operation%20Manual.md)\n\n## Error Troubleshooting\n\n[ProxyCat Investigation Manual](../main/ProxyCat-Manual/Investigation%20Manual.md)\n\n## Disclaimer\n\n- By downloading, installing, using, or modifying this tool and related code, you indicate your trust in this tool.\n- We are not responsible for any form of loss or damage caused to yourself or others while using this tool.\n- You are solely responsible for any illegal activities conducted while using this tool.\n- Please carefully read and fully understand all terms, especially liability exemption clauses.\n- You have no right to download, install, or use this tool unless you have read and accepted all terms.\n- Your download, installation, and usage actions indicate your acceptance of this agreement.\n\n## Changelog\n\n[Changelog Records](../main/ProxyCat-Manual/logs.md)\n\n## Development Plan\n\n- [x] Add detailed logging to record all IP identities connecting to ProxyCat, supporting multiple users.\n- [x] Add Web UI for a more powerful and user-friendly interface.\n- [ ] Develop babycat module that can run on any server or host to turn it into a proxy server.\n- [ ] Add request blacklist/whitelist to specify URLs, IPs, or domains to be forcibly dropped or bypassed.\n- [ ] Package to PyPi for easier installation and use.\n\nIf you have good ideas or encounter bugs during use, please contact the author through:\n\nWeChat Official Account: **樱花庄的本间白猫**\n\n## Special Thanks\n\nIn no particular order, thanks to all contributors who helped with this project:\n\n- [AabyssZG (曾哥)](https://github.com/AabyssZG)\n- [ProbiusOfficial (探姬)](https://github.com/ProbiusOfficial)\n- [gh0stkey (EvilChen)](https://github.com/gh0stkey)\n- [huangzheng2016(HydrogenE7)](https://github.com/huangzheng2016)\n- chars6\n- qianzai（千载）\n- ziwindlu\n\n## Sponsor\n\nOpen source development isn't easy. If you find this tool helpful, consider sponsoring the author's development!\n\n---\n| Rank |         ID          | Amount (CNY) |\n| :--: | :-----------------: | :----------: |\n|  1   |      **陆沉**       |   1266.62    |\n|  2   | **柯林斯.民间新秀** |     696      |\n|  3   |      **北**      |     170     |\n|  [Sponsor List](https://github.com/honmashironeko/Thanks-for-sponsorship)   |     Every sponsorship is a motivation for the author!      |      (´∀｀)♡      |\n\n---\n![Sponsor](./assets/赞助.png)\n\n## Proxy Recommendations\n\n- [First affordable proxy service - Get 5000 free IPs + ¥10 coupon with invite code](https://h.shanchendaili.com/invite_reg.html?invite=fM6fVG)\n- [Various carrier data plans](https://172.lot-ml.com/ProductEn/Index/0b7c9adef5e9648f)\n- [Click here to purchase](https://www.ipmart.io?source=Shironeko)\n"
  },
  {
    "path": "README.md",
    "content": "![ProxyCat](https://socialify.git.ci/honmashironeko/ProxyCat/image?custom_description=%E4%B8%80%E6%AC%BE%E6%94%AF%E6%8C%81%E5%A4%9A%E5%8D%8F%E8%AE%AE%E7%9A%84%E9%9A%A7%E9%81%93%E4%BB%A3%E7%90%86%E6%B1%A0%E4%B8%AD%E9%97%B4%E4%BB%B6%E6%9C%8D%E5%8A%A1%EF%BC%8C%E5%AE%9E%E7%8E%B0%E4%BD%8E%E6%88%90%E6%9C%AC%E4%BB%A3%E7%90%86%E7%9A%84%E8%87%AA%E5%8A%A8%E8%BD%AE%E6%8D%A2&description=1&font=Bitter&forks=1&issues=1&language=1&logo=https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F139044047%3Fv%3D4%26size%3D1080&name=1&owner=1&pattern=Circuit+Board&pulls=1&stargazers=1&theme=Dark)\n\n<p align=\"center\">\n  <a href=\"/README-EN.md\">English</a>\n  ·\n  <a href=\"/README.md\">简体中文</a>\n</p>\n\n## 目录\n\n- [开发缘由](#开发缘由)\n- [功能特点](#功能特点)\n- [安装与使用](#安装与使用)\n- [免责申明](#免责申明)\n- [更新日志](#更新日志)\n- [开发计划](#开发计划)\n- [特别鸣谢](#特别鸣谢)\n- [赞助开源](#赞助开源)\n- [代理推荐](#代理推荐)\n\n## 开发缘由\n\n在渗透过程中，经常需要隐藏或更换IP地址以绕过安全设备。然而，市面上的隧道代理价格高昂，普遍在20-40元/天，这对于许多人来说难以接受。笔者注意到，短效IP的性价比很高，一个IP只需几分钱，平均每天0.2-3元。\n\n综上所述，**ProxyCat** 应运而生！本工具旨在将持续时间仅有1分钟至60分钟不等的短效IP转变为固定IP供其他工具使用，形成代理池服务器，部署一次即可永久使用。\n\n![项目原理图](./assets/项目原理图.png)\n\n## 功能特点\n\n- **两种协议监听**：支持 HTTP/SOCKS5 协议监听，兼容更多工具。\n- **三种代理地址**：支持 HTTP/HTTPS/SOCKS5 代理服务器及身份鉴别。\n- **灵活切换模式**：支持顺序、随机及自定义代理选择，优化流量分配。\n- **动态获取代理**：通过 GetIP 函数即时获取可用代理，支持 API 接口调用。\n- **代理保护机制**：在使用 GetIP 方式获取代理时，首次运行不会直接请求获取，将会在收到请求的时候才获取。\n- **自动代理检测**：启动时自动检测代理有效性，剔除无效代理。\n- **智能切换代理**：仅在请求运行时获取新代理，减少资源消耗。\n- **失效代理切换**：代理失效后自动验证切换新代理，确保不中断服务。\n- **身份认证支持**：支持用户名/密码认证和黑白名单管理，提高安全性。\n- **实时状态显示**：展示代理状态和切换时间，实时掌握代理动态。\n- **动态更新配置**：无需重启服务，动态检测配置并更新。\n- **Web UI界面**：提供 Web 管理界面，操作管理更加便捷。\n- **Docker部署**：Docker 一键部署，Web 统一管理。\n- **中英文双语**：支持中文英文一键切换。\n- **配置灵活**：通过 config.ini 文件自定义端口、模式和认证信息等。\n- **版本检测**：自动检查软件更新，保证版本最新。\n\n## 工具使用\n\n[ProxyCat操作手册](../main/ProxyCat-Manual/Operation%20Manual.md)\n\n## 报错排查\n\n[ProxyCat排查手册](../main/ProxyCat-Manual/Investigation%20Manual.md)\n\n## 免责申明\n\n- 如果您下载、安装、使用、修改本工具及相关代码，即表明您信任本工具。\n- 在使用本工具时造成对您自己或他人任何形式的损失和伤害，我们不承担任何责任。\n- 如您在使用本工具的过程中存在任何非法行为，您需自行承担相应后果，我们将不承担任何法律及连带责任。\n- 请您务必审慎阅读、充分理解各条款内容，特别是免除或者限制责任的条款，并选择接受或不接受。\n- 除非您已阅读并接受本协议所有条款，否则您无权下载、安装或使用本工具。\n- 您的下载、安装、使用等行为即视为您已阅读并同意上述协议的约束。\n\n## 更新日志\n\n[更新日志记录](../main/ProxyCat-Manual/logs.md)\n\n## 开发计划\n\n- [ ] **增加请求切换IP配置**：支持设置随机范围内的请求次数来切换IP，提升对抗阈值类防御的能力。\n- [ ] **babycat模块**：通过部署babycat子模块，可以在任意服务器或主机上快速搭建代理服务器，并通过Web端实现统一管理，简化操作流程。（针对红队进行开发强化）\n- [ ] **爬虫代理池**：利用爬虫抓取免费代理地址，构建代理池。该代理池能够持续维护可用的高质量代理资源，支持负载均衡下的随机调用，同时允许用户指定IP归属地，满足不同场景需求。\n- [ ] **机场协议支持**：接入机场协议后，可将每个节点作为代理地址使用，扩展代理服务器的功能和灵活性。\n- [ ] **域名/IP黑白名单**：提供目标域名或IP的黑白名单配置功能，类似VPN规则模式，实现伪全局代理效果，确保特定流量按需转发。\n- [ ] **版本自动升级**：内置版本自动升级模块，确保软件始终运行在最新版本，减少手动维护的工作量，同时提升安全性与兼容性。\n\n如果您有好的创意，或在使用过程中遇到bug，请通过以下方式联系作者反馈！\n\n微信公众号：**樱花庄的本间白猫**\n\n## 特别鸣谢\n\n本排名不分先后，感谢为本项目提供帮助的师傅们。\n\n- [AabyssZG (曾哥)](https://github.com/AabyssZG)\n- [ProbiusOfficial (探姬)](https://github.com/ProbiusOfficial)\n- [gh0stkey (EvilChen)](https://github.com/gh0stkey)\n- [huangzheng2016(HydrogenE7)](https://github.com/huangzheng2016)\n- chars6\n- qianzai（千载）\n- ziwindlu\n- yuzhegan\n- 摘星怪\n\n## 赞助开源\n\n开源不易，如果您觉得工具不错，或许可以试着赞助一下作者的开发哦~\n\n---\n| 排名 |         ID          | 赞助金额（元） |\n| :--: | :-----------------: | :------------: |\n|  1   |      **陆沉**       |    1355.5    |\n|  2   | **乡村牛公子** |      976      |\n|  3   |      **柯林斯.民间新秀**      |      696      |\n|  [赞助榜单](https://github.com/honmashironeko/Thanks-for-sponsorship)   |     您的每一份赞助都是作者源源不断的动力！      |      (´∀｀)♡       |\n\n---\n![赞助](./assets/赞助.png)\n\n## 代理推荐\n\n- [第一家便宜大碗代理购买，用邀请码注册得5000免费IP+10元优惠券](https://h.shanchendaili.com/invite_reg.html?invite=fM6fVG)\n- [各大运营商流量卡](https://172.lot-ml.com/ProductEn/Index/0b7c9adef5e9648f)\n- [国外匿名代理](https://www.ipmart.io?source=Shironeko)\n\n![Star History Chart](https://api.star-history.com/svg?repos=honmashironeko/ProxyCat&type=Date)\n"
  },
  {
    "path": "app.py",
    "content": "from flask import Flask, render_template, jsonify, request, redirect, url_for, send_from_directory\nimport sys\nimport os\nimport logging\nfrom datetime import datetime\nimport json\nfrom configparser import ConfigParser\nfrom itertools import cycle\nimport werkzeug.serving\nfrom functools import wraps\n\nsys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\n\nfrom ProxyCat import run_server\nfrom modules.modules import load_config, check_proxies, get_message, load_ip_list\nfrom modules.proxyserver import AsyncProxyServer\nimport asyncio\nimport threading\nimport time\n\napp = Flask(__name__, \n           template_folder='web/templates',\n           static_folder='web/static') \n\nwerkzeug.serving.WSGIRequestHandler.log = lambda self, type, message, *args: None\nlogging.getLogger('werkzeug').setLevel(logging.ERROR)\n\nconfig = load_config('config/config.ini')\nserver = AsyncProxyServer(config)\n\ndef get_config_path(filename):\n    return os.path.join('config', filename)\n\nlog_file = 'logs/proxycat.log'\nos.makedirs('logs', exist_ok=True)\nlog_messages = []\nmax_log_messages = 10000\n\nclass CustomFormatter(logging.Formatter):\n    def formatTime(self, record, datefmt=None):\n        return datetime.fromtimestamp(record.created).strftime('%Y-%m-%d %H:%M:%S')\n\ndef setup_logging():\n    file_formatter = CustomFormatter('%(asctime)s - %(levelname)s - %(message)s')\n    file_handler = logging.FileHandler(log_file, encoding='utf-8')\n    file_handler.setFormatter(file_formatter)\n\n    console_handler = logging.StreamHandler()\n    console_formatter = CustomFormatter('%(asctime)s - %(levelname)s - %(message)s')\n    console_handler.setFormatter(console_formatter)\n\n    memory_handler = MemoryHandler()\n    memory_handler.setFormatter(CustomFormatter('%(message)s'))\n\n    root_logger = logging.getLogger()\n    root_logger.setLevel(logging.INFO)\n    for handler in root_logger.handlers[:]:\n        root_logger.removeHandler(handler)\n    root_logger.addHandler(file_handler)\n    root_logger.addHandler(console_handler)\n    root_logger.addHandler(memory_handler)\n\nclass MemoryHandler(logging.Handler):\n    def emit(self, record):\n        global log_messages\n        log_messages.append({\n            'time': datetime.fromtimestamp(record.created).strftime('%Y-%m-%d %H:%M:%S'),\n            'level': record.levelname,\n            'message': self.format(record)\n        })\n        if len(log_messages) > max_log_messages:\n            log_messages = log_messages[-max_log_messages:]\n\ndef require_token(f):\n    @wraps(f)\n    def decorated_function(*args, **kwargs):\n        token = request.args.get('token')\n        config_token = server.config.get('token', '')\n        \n        if not config_token:\n            return f(*args, **kwargs)\n            \n        if not token or token != config_token:\n            return jsonify({\n                'status': 'error',\n                'message': get_message('invalid_token', server.language)\n            }), 401\n            \n        return f(*args, **kwargs)\n    return decorated_function\n\n@app.route('/')\ndef root():\n    token = request.args.get('token')\n    if token:\n        return redirect(f'/web?token={token}')\n    return redirect('/web')\n\n@app.route('/web')\n@require_token\ndef web():\n    return render_template('index.html')\n\n@app.route('/api/status')\n@require_token\ndef get_status():\n    with open('config/config.ini', 'r', encoding='utf-8') as f:\n        config_content = f.read()\n        \n    config = ConfigParser()\n    config.read('config/config.ini', encoding='utf-8')\n    \n    server_config = dict(config.items('Server')) if config.has_section('Server') else {}\n    \n    current_proxy = server.current_proxy\n    if not current_proxy and not server.use_getip:\n        if hasattr(server, 'proxies') and server.proxies:\n            current_proxy = server.proxies[0]\n        else:\n            current_proxy = get_message('no_proxy', server.language)\n    \n    \n    time_left = server.time_until_next_switch()\n    if server.mode == 'loadbalance':\n        \n        if time_left == float('inf'):\n            time_left = -1\n    \n    return jsonify({\n        'current_proxy': current_proxy,\n        'mode': server.mode,\n        'port': int(server_config.get('port', '1080')),\n        'interval': server.interval,\n        'time_left': time_left,\n        'total_proxies': len(server.proxies) if hasattr(server, 'proxies') else 0,\n        'use_getip': server.use_getip,\n        'getip_url': getattr(server, 'getip_url', '') if getattr(server, 'use_getip', False) else '',\n        'auth_required': server.auth_required,\n        'display_level': int(server_config.get('display_level', '1')),\n        'service_status': 'running' if server.running else 'stopped',\n        'config': server_config\n    })\n\n@app.route('/api/config', methods=['POST'])\ndef save_config():\n    try:\n        new_config = request.get_json()\n        current_config = load_config('config/config.ini')\n        port_changed = str(new_config.get('port', '')) != str(current_config.get('port', ''))\n        mode_changed = new_config.get('mode', '') != current_config.get('mode', '')\n        use_getip_changed = (new_config.get('use_getip', 'False').lower() == 'true') != (current_config.get('use_getip', 'False').lower() == 'true')\n        \n        config_parser = ConfigParser()\n        config_parser.read('config/config.ini', encoding='utf-8')\n        \n        if not config_parser.has_section('Server'):\n            config_parser.add_section('Server')\n            \n        for key, value in new_config.items():\n            if key != 'users':\n                config_parser.set('Server', key, str(value))\n        \n        with open('config/config.ini', 'w', encoding='utf-8') as f:\n            config_parser.write(f)\n            \n        \n        old_mode = server.mode\n        old_use_getip = server.use_getip\n        \n        \n        server.config = load_config('config/config.ini')\n        server._init_config_values(server.config)\n        \n        \n        if mode_changed or use_getip_changed:\n            server._handle_mode_change()\n            \n            \n            if new_config.get('mode') == 'loadbalance':\n                server.last_switch_time = time.time()\n                server.last_switch_attempt = 0\n        \n        return jsonify({\n            'status': 'success',\n            'port_changed': port_changed,\n            'service_status': 'running' if server.running else 'stopped'\n        })\n        \n    except Exception as e:\n        logging.error(f\"Error saving config: {e}\")\n        return jsonify({\n            'status': 'error',\n            'message': str(e)\n        })\n\n@app.route('/api/proxies', methods=['GET', 'POST'])\ndef handle_proxies():\n    if request.method == 'POST':\n        try:\n            proxies = request.json.get('proxies', [])\n            proxy_file = get_config_path(os.path.basename(server.proxy_file))\n            with open(proxy_file, 'w', encoding='utf-8') as f:\n                f.write('\\n'.join(proxies))\n            server.proxies = server._load_file_proxies()\n            if server.proxies:\n                server.proxy_cycle = cycle(server.proxies)\n                server.current_proxy = next(server.proxy_cycle)\n            return jsonify({\n                'status': 'success',\n                'message': get_message('proxy_save_success', server.language)\n            })\n        except Exception as e:\n            return jsonify({\n                'status': 'error',\n                'message': get_message('proxy_save_failed', server.language, str(e))\n            })\n    else:\n        try:\n            proxy_file = get_config_path(os.path.basename(server.proxy_file))\n            with open(proxy_file, 'r', encoding='utf-8') as f:\n                proxies = f.read().splitlines()\n            return jsonify({'proxies': proxies})\n        except Exception:\n            return jsonify({'proxies': []})\n\n@app.route('/api/check_proxies')\ndef check_proxies_api():\n    try:\n        test_url = request.args.get('test_url', 'https://www.baidu.com')\n        valid_proxies = asyncio.run(check_proxies(server.proxies, test_url))\n        total_valid = len(valid_proxies)\n        return jsonify({\n            'status': 'success',\n            'valid_proxies': valid_proxies,\n            'total': total_valid,\n            'message': get_message('proxy_check_result', server.language, total_valid)\n        })\n    except Exception as e:\n        return jsonify({\n            'status': 'error',\n            'message': get_message('proxy_check_failed', server.language, str(e))\n        })\n\n@app.route('/api/ip_lists', methods=['GET', 'POST'])\ndef handle_ip_lists():\n    if request.method == 'POST':\n        try:\n            list_type = request.json.get('type')\n            ip_list = request.json.get('list', [])\n            base_filename = os.path.basename(server.whitelist_file if list_type == 'whitelist' else server.blacklist_file)\n            filename = get_config_path(base_filename)\n            \n            with open(filename, 'w', encoding='utf-8') as f:\n                f.write('\\n'.join(ip_list))\n            \n            if list_type == 'whitelist':\n                server.whitelist = load_ip_list(filename)\n            else:\n                server.blacklist = load_ip_list(filename)\n                \n            return jsonify({\n                'status': 'success',\n                'message': get_message('ip_list_save_success', server.language)\n            })\n        except Exception as e:\n            return jsonify({\n                'status': 'error',\n                'message': get_message('ip_list_save_failed', server.language, str(e))\n            })\n    else:\n        whitelist_file = get_config_path(os.path.basename(server.whitelist_file))\n        blacklist_file = get_config_path(os.path.basename(server.blacklist_file))\n        return jsonify({\n            'whitelist': list(load_ip_list(whitelist_file)), \n            'blacklist': list(load_ip_list(blacklist_file)) \n        })\n\n@app.route('/api/logs')\ndef get_logs():\n    try:\n        start = int(request.args.get('start', 0))\n        limit = int(request.args.get('limit', 100))\n        level = request.args.get('level', 'ALL')\n        search = request.args.get('search', '').lower()\n        \n        filtered_logs = log_messages\n        \n        if level != 'ALL':\n            filtered_logs = [log for log in log_messages if log['level'] == level]\n        \n        if search:\n            filtered_logs = [\n                log for log in filtered_logs \n                if search in log['message'].lower() or \n                   search in log['level'].lower() or \n                   search in log['time'].lower()\n            ]\n        \n        return jsonify({\n            'logs': filtered_logs[start:start+limit],\n            'total': len(filtered_logs),\n            'status': 'success'\n        })\n    except Exception as e:\n        return jsonify({\n            'status': 'error',\n            'message': str(e)\n        })\n\n@app.route('/api/logs/clear', methods=['POST'])\ndef clear_logs():\n    try:\n        global log_messages\n        log_messages = []\n        \n        with open(log_file, 'w', encoding='utf-8') as f:\n            f.write('')\n            \n        return jsonify({\n            'status': 'success',\n            'message': get_message('logs_cleared', server.language)\n        })\n    except Exception as e:\n        return jsonify({\n            'status': 'error',\n            'message': get_message('clear_logs_failed', server.language, str(e))\n        })\n\n@app.route('/api/switch_proxy')\n@require_token\ndef switch_proxy():\n    try:\n        result = asyncio.run(server.switch_proxy())\n        if result:\n            return jsonify({\n                'status': 'success',\n                'current_proxy': server.current_proxy,\n                'message': get_message('switch_success', server.language)\n            })\n        else:\n            return jsonify({\n                'status': 'error',\n                'message': get_message('switch_failed', server.language, 'Proxy switch not needed or in cooldown')\n            })\n    except Exception as e:\n        return jsonify({\n            'status': 'error',\n            'message': get_message('switch_failed', server.language, str(e))\n        })\n\n@app.route('/api/service', methods=['POST'])\n@require_token\ndef control_service():\n    try:\n        action = request.json.get('action')\n        if action == 'start':\n            if not server.running:\n                server.stop_server = False\n                if hasattr(server, 'proxy_thread') and server.proxy_thread and server.proxy_thread.is_alive():\n                    server.proxy_thread.join(timeout=5)\n                server.proxy_thread = threading.Thread(target=lambda: asyncio.run(run_server(server)), daemon=True)\n                server.proxy_thread.start()\n                \n                for _ in range(10):\n                    if server.running:\n                        break\n                    time.sleep(0.5)\n                    \n                if server.running:\n                    return jsonify({\n                        'status': 'success',\n                        'message': get_message('service_start_success', server.language),\n                        'service_status': 'running'\n                    })\n                else:\n                    return jsonify({\n                        'status': 'error',\n                        'message': get_message('service_start_failed', server.language),\n                        'service_status': 'stopped'\n                    })\n            return jsonify({\n                'status': 'success',\n                'message': get_message('service_already_running', server.language),\n                'service_status': 'running'\n            })\n            \n        elif action == 'stop':\n            if server.running:\n                server.stop_server = True\n                server.running = False\n                \n                if server.server_instance:\n                    server.server_instance.close()\n                \n                if hasattr(server, 'proxy_thread') and server.proxy_thread:\n                    server.proxy_thread = None\n                \n                for _ in range(5):\n                    if server.server_instance is None:\n                        break\n                    time.sleep(0.2)\n                \n                return jsonify({\n                    'status': 'success',\n                    'message': get_message('service_stop_success', server.language),\n                    'service_status': 'stopped'\n                })\n            return jsonify({\n                'status': 'success',\n                'message': get_message('service_not_running', server.language),\n                'service_status': 'stopped'\n            })\n            \n        elif action == 'restart':\n            if server.running:\n                server.stop_server = True\n                if server.server_instance:\n                    server.server_instance.close()\n                \n                for _ in range(10):\n                    if not server.running:\n                        break\n                    time.sleep(0.5)\n                \n                if server.running:\n                    if hasattr(server, 'proxy_thread') and server.proxy_thread:\n                        server.proxy_thread = None\n                    server.running = False\n            \n            server.stop_server = False\n            server.proxy_thread = threading.Thread(target=lambda: asyncio.run(run_server(server)), daemon=True)\n            server.proxy_thread.start()\n            \n            for _ in range(10):\n                if server.running:\n                    break\n                time.sleep(0.5)\n            \n            if server.running:\n                return jsonify({\n                    'status': 'success',\n                    'message': get_message('service_restart_success', server.language)\n                })\n            else:\n                return jsonify({\n                    'status': 'error',\n                    'message': get_message('service_restart_failed', server.language)\n                })\n            \n        return jsonify({\n            'status': 'error',\n            'message': get_message('invalid_action', server.language)\n        })\n    except Exception as e:\n        return jsonify({\n            'status': 'error',\n            'message': get_message('operation_failed', server.language, str(e))\n        })\n\n@app.route('/api/language', methods=['POST'])\ndef change_language():\n    try:\n        new_language = request.json.get('language', 'cn')\n        if new_language not in ['cn', 'en']:\n            return jsonify({\n                'status': 'error',\n                'message': get_message('unsupported_language', server.language)\n            })\n        \n        config = ConfigParser()\n        config.read('config/config.ini', encoding='utf-8')\n        \n        if 'Server' not in config:\n            config.add_section('Server')\n        \n        config.set('Server', 'language', new_language)\n        \n        with open('config/config.ini', 'w', encoding='utf-8') as f:\n            config.write(f)\n            \n        server.language = new_language\n        \n        return jsonify({\n            'status': 'success',\n            'language': new_language\n        })\n    except Exception as e:\n        return jsonify({\n            'status': 'error',\n            'message': get_message('operation_failed', server.language, str(e))\n        })\n\n@app.route('/api/version')\ndef check_version():\n    try:\n        import re\n        import httpx\n        from packaging import version\n        import logging\n        \n        httpx_logger = logging.getLogger('httpx')\n        original_level = httpx_logger.level\n        httpx_logger.setLevel(logging.WARNING)\n        \n        CURRENT_VERSION = \"ProxyCat-V2.0.4\"\n        \n        try:\n            client = httpx.Client(transport=httpx.HTTPTransport(retries=3))\n            response = client.get(\"https://y.shironekosan.cn/1.html\", timeout=10)\n            response.raise_for_status()\n            content = response.text\n            \n            match = re.search(r'<p>(ProxyCat-V\\d+\\.\\d+\\.\\d+)</p>', content)\n            if match:\n                latest_version = match.group(1)\n                is_latest = version.parse(latest_version.split('-V')[1]) <= version.parse(CURRENT_VERSION.split('-V')[1])\n                \n                return jsonify({\n                    'status': 'success',\n                    'is_latest': is_latest,\n                    'current_version': CURRENT_VERSION,\n                    'latest_version': latest_version\n                })\n            else:\n                return jsonify({\n                    'status': 'error',\n                    'message': get_message('version_info_not_found', server.language)\n                })\n        finally:\n            httpx_logger.setLevel(original_level)\n            \n    except Exception as e:\n        return jsonify({\n            'status': 'error',\n            'message': get_message('update_check_error', server.language, str(e))\n        })\n\n@app.route('/api/users', methods=['GET', 'POST'])\n@require_token\ndef handle_users():\n    if request.method == 'POST':\n        try:\n            users = request.json.get('users', {})\n            config = ConfigParser()\n            config.read('config/config.ini', encoding='utf-8')\n            \n            sections_to_preserve = {}\n            for section in config.sections():\n                if section != 'Users':\n                    sections_to_preserve[section] = dict(config.items(section))\n\n            config = ConfigParser()\n            \n            for section, options in sections_to_preserve.items():\n                config.add_section(section)\n                for key, value in options.items():\n                    config.set(section, key, value)\n            \n            if users:\n                config.add_section('Users')\n                for username, password in users.items():\n                    config.set('Users', username, password)\n            \n            with open('config/config.ini', 'w', encoding='utf-8') as f:\n                config.write(f)\n            \n            server.users = users\n            server.auth_required = bool(users)\n            \n            if hasattr(server, 'proxy_server') and server.proxy_server:\n                server.proxy_server.users = users\n                server.proxy_server.auth_required = bool(users)\n            \n            return jsonify({\n                'status': 'success',\n                'message': get_message('users_save_success', server.language)\n            })\n        except Exception as e:\n            return jsonify({\n                'status': 'error',\n                'message': get_message('users_save_failed', server.language, str(e))\n            })\n    else:\n        try:\n            config = ConfigParser()\n            config.read('config/config.ini', encoding='utf-8')\n            users = {}\n            if config.has_section('Users'):\n                users = dict(config.items('Users'))\n            return jsonify({'users': users})\n        except Exception as e:\n            logging.error(f\"Error getting users: {e}\")\n            return jsonify({'users': {}})\n\n@app.route('/static/<path:path>')\ndef send_static(path):\n    return send_from_directory('web/static', path)\n\ndef run_proxy_server():\n    try:\n        asyncio.run(run_server(server))\n    except KeyboardInterrupt:\n        logging.info(get_message('user_interrupt', server.language))\n    except Exception as e:\n        logging.error(f\"Proxy server error: {e}\")\n\nif __name__ == '__main__':\n    setup_logging()\n    web_port = int(config.get('web_port', '5000'))\n    web_url = f\"http://127.0.0.1:{web_port}\"\n    if config.get('token'):\n        web_url += f\"/web?token={config.get('token')}\"\n    \n    logging.info(get_message('web_panel_url', server.language, web_url))\n    logging.info(get_message('web_panel_notice', server.language))\n\n    proxy_thread = threading.Thread(target=run_proxy_server, daemon=True)\n    proxy_thread.start()\n    \n    app.run(host='0.0.0.0', port=web_port)"
  },
  {
    "path": "config/blacklist.txt",
    "content": ""
  },
  {
    "path": "config/config.ini",
    "content": "[Server]\ndisplay_level = 1\nport = 1080\nweb_port = 5000\nmode = cycle\ninterval = 300\nuse_getip = false\ngetip_url = http://example.com/getip\nproxy_username = \nproxy_password = \nproxy_file = ip.txt\ncheck_proxies = true\ntest_url = \nlanguage = cn\nwhitelist_file = whitelist.txt\nblacklist_file = blacklist.txt\nip_auth_priority = whitelist\ntoken = honmashironeko\n\n[Users]\nneko = 123456\nk = 123\n\n"
  },
  {
    "path": "config/getip.py",
    "content": "from modules.modules import get_message, load_config\nimport requests\nimport time\n\ndef newip():\n    config = load_config()\n    language = config.get('language', 'cn')\n\n    def handle_error(error_type, details=None):\n        error_msg = 'whitelist_error' if error_type == 'whitelist' else 'proxy_file_not_found'\n        print(get_message(error_msg, language, str(details)))\n        raise ValueError(f\"{error_type}: {details}\")\n\n    try:\n        url = config.get('getip_url', '')\n        username = config.get('proxy_username', '')\n        password = config.get('proxy_password', '')\n        \n        if not url:\n            raise ValueError('getip_url')\n            \n        def get_proxy():\n            response = requests.get(url)\n            response.raise_for_status()\n            return response.text.split(\"\\r\\n\")[0]\n            \n        proxy = get_proxy()\n        if proxy == \"error000x-13\":\n            appKey = \"\"\n            anquanma = \"\"\n            whitelist_url = f\"https://sch.shanchendaili.com/api.html?action=addWhiteList&appKey={appKey}&anquanma={anquanma}\"\n            requests.get(whitelist_url).raise_for_status()\n            time.sleep(1)\n            proxy = get_proxy()\n        \n        if username and password:\n            return f\"socks5://{username}:{password}@{proxy}\"\n        return f\"socks5://{proxy}\"\n        \n    except requests.RequestException as e:\n        handle_error('request', e)\n    except ValueError as e:\n        handle_error('config', e)\n    except Exception as e:\n        handle_error('unknown', e)\n\n\n"
  },
  {
    "path": "config/ip.txt",
    "content": "http://127.0.0.1:7890\nsocks5://127.0.0.1:7890"
  },
  {
    "path": "config/whitelist.txt",
    "content": ""
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3'\nservices:\n  proxycat:\n    build: .\n    environment:\n      - TZ=Asia/Shanghai\n    ports:\n      - \"1080:1080\"\n      - \"5000:5000\"\n    volumes:\n      - ./config:/app/config\n    restart: unless-stopped\n    network_mode: \"bridge\""
  },
  {
    "path": "logs/proxycat.log",
    "content": ""
  },
  {
    "path": "modules/modules.py",
    "content": "import asyncio, logging, random, httpx, re, os, time\nfrom configparser import ConfigParser\nfrom packaging import version\nfrom colorama import Fore, Style\n\nclass ColoredFormatter(logging.Formatter):\n    COLORS = {\n        logging.INFO: Fore.GREEN,\n        logging.WARNING: Fore.YELLOW,\n        logging.ERROR: Fore.RED,\n        logging.CRITICAL: Fore.RED + Style.BRIGHT,\n    }\n\n    def format(self, record):\n        log_color = self.COLORS.get(record.levelno, Fore.WHITE)\n        record.msg = f\"{log_color}{record.msg}{Style.RESET_ALL}\"\n        return super().format(record)\n\nMESSAGES = {\n    'cn': {\n        'getting_new_proxy': '正在获取新的代理IP',\n        'new_proxy_is': '新的代理IP为: {}',\n        'proxy_check_start': '开始检测代理地址...',\n        'proxy_check_disabled': '代理检测已禁用',\n        'valid_proxies': '有效代理地址: {}',\n        'no_valid_proxies': '没有有效的代理地址',\n        'proxy_check_failed': '{}代理 {} 检测失败: {}',\n        'proxy_switch': '切换代理: {} -> {}',\n        'proxy_consecutive_fails': '代理 {} 连续失败 {} 次，正在切换新代理',\n        'proxy_invalid': '代理 {} 无效，立即切换代理',\n        'proxy_failure': '代理检测失败: {}',\n        'proxy_failure_threshold': '代理无效，开始切换',\n        'proxy_check_error': '代理检查时发生错误: {}',\n        'connection_timeout': '连接超时',\n        'data_transfer_timeout': '数据传输超时，正在重试...',\n        'connection_reset': '连接被重置',\n        'transfer_cancelled': '传输被取消',\n        'data_transfer_error': '数据传输错误: {}',\n        'unsupported_protocol': '不支持的协议请求: {}',\n        'client_error': '客户端处理出错: {}',\n        'response_write_error': '响应写入错误: {}',\n        'server_closing': '服务器正在关闭...',\n        'program_interrupted': '程序被用户中断',\n        'multiple_proxy_fail': '多次尝试获取有效代理失败，退出程序',\n        'current_proxy': '当前代理',\n        'next_switch': '下次切换',\n        'seconds': '秒',\n        'no_proxies_available': '没有可用的代理',\n        'proxy_file_not_found': '代理文件不存在: {}',\n        'auth_not_set': '未设置 (无需认证)',\n        'public_account': '公众号',\n        'blog': '博客',\n        'proxy_mode': '代理轮换模式',\n        'cycle': '循环',\n        'loadbalance': '负载均衡',\n        'single_round': '单轮',\n        'proxy_interval': '代理更换时间',\n        'default_auth': '默认账号密码',\n        'local_http': '本地监听地址 (HTTP)',\n        'local_socks5': '本地监听地址 (SOCKS5)',\n        'star_project': '开源项目求 Star',\n        'client_handle_error': '客户端处理错误: {}',\n        'proxy_invalid_switch': '代理无效，切换代理',\n        'request_fail_retry': '请求失败，重试剩余次数: {}',\n        'user_interrupt': '用户中断程序',\n        'new_version_found': '发现新版本！',\n        'visit_quark': '夸克网盘: https://pan.quark.cn/s/39b4b5674570',\n        'visit_github': 'GitHub: https://github.com/honmashironeko/ProxyCat',\n        'visit_baidu': '百度网盘: https://pan.baidu.com/s/1C9LVC9aiaQeYFSj_2mWH1w?pwd=13r5',\n        'latest_version': '当前已是最新版本',\n        'version_info_not_found': '未找到版本信息',\n        'update_check_error': '检查更新失败: {}',\n        'unauthorized_ip': '未授权的IP尝试访问: {}',\n        'client_cancelled': '客户端连接已取消',\n        'socks5_connection_error': 'SOCKS5连接错误: {}',\n        'connect_timeout': '连接超时',\n        'connection_reset': '连接被重置',\n        'transfer_cancelled': '传输已取消',\n        'client_request_error': '客户端请求错误: {}',\n        'unsupported_protocol': '不支持的协议: {}',\n        'request_retry': '请求失败，重试中 (剩余{}次)',\n        'response_write_error': '写入响应时出错: {}',\n        'consecutive_failures': '检测到连续代理失败: {}',\n        'invalid_proxy': '当前代理无效: {}',\n        'whitelist_error': '添加白名单失败: {}',\n        'api_mode_notice': '当前为API模式，收到请求将自动获取代理地址',\n        'server_running': '代理服务器运行在 {}:{}',\n        'server_start_error': '服务器启动错误: {}',\n        'server_shutting_down': '正在关闭服务器...',\n        'client_process_error': '处理客户端请求时出错: {}',\n        'request_handling_error': '请求处理错误: {}',\n        'proxy_forward_error': '代理转发错误: {}',\n        'data_transfer_timeout': '{}数据传输超时',\n        'data_transfer_error': '{}数据传输错误: {}',\n        'status_update_error': '状态更新出错',\n        'display_level_notice': '当前显示级别: {}',\n        'display_level_desc': '''显示级别说明:\n0: 仅显示代理切换和错误信息\n1: 显示代理切换、倒计时和错误信息\n2: 显示所有详细信息''',\n        'new_client_connect': '新客户端连接 - IP: {}, 用户: {}',\n        'no_auth': '无认证',\n        'connection_error': '连接处理错误: {}',\n        'cleanup_error': '清理IP错误: {}',\n        'port_changed': '端口已更改: {} -> {}，需要重启服务器生效',\n        'config_updated': '服务器配置已更新',\n        'load_proxy_file_error': '加载代理文件失败: {}',\n        'proxy_check_result': '代理检查完成，有效代理：{}个',\n        'no_proxy': '无代理',\n        'cycle_mode': '循环模式',\n        'loadbalance_mode': '负载均衡模式',\n        'proxy_check_start': '开始检查代理...',\n        'proxy_check_complete': '代理检查完成',\n        'proxy_save_success': '代理保存成功',\n        'proxy_save_failed': '代理保存失败: {}',\n        'ip_list_save_success': 'IP名单保存成功',\n        'ip_list_save_failed': 'IP名单保存失败: {}',\n        'switch_success': '代理切换成功',\n        'switch_failed': '代理切换失败: {}',\n        'service_start_success': '服务启动成功',\n        'service_start_failed': '服务启动失败',\n        'service_already_running': '服务已在运行',\n        'service_stop_success': '服务停止成功',\n        'service_not_running': '服务未在运行',\n        'service_restart_success': '服务重启成功',\n        'service_restart_failed': '服务重启失败',\n        'invalid_action': '无效的操作',\n        'operation_failed': '操作失败: {}',\n        'logs_cleared': '日志已清除',\n        'clear_logs_failed': '清除日志失败: {}',\n        'unsupported_language': '不支持的语言',\n        'language_changed': '语言已切换为{}',\n        'loading': '加载中...',\n        'get_proxy_failed': '获取新代理失败: {}',\n        'log_level_all': '全部',\n        'log_level_info': '信息',\n        'log_level_warning': '警告',\n        'log_level_error': '错误',\n        'log_level_critical': '严重错误',\n        'confirm_clear_logs': '确定要清除所有日志吗？此操作不可恢复。',\n        'language_label': '语言',\n        'chinese': '中文',\n        'english': 'English',\n        'manual_switch_btn': '手动切换',\n        'service_control_title': '服务控制',\n        'language_switch_success': '',\n        'language_switch_failed': '',\n        'refresh_failed': '刷新数据失败: {}',\n        'auth_username_label': '认证用户名',\n        'auth_password_label': '认证密码',\n        'proxy_auth_username_label': '代理认证用户名',\n        'proxy_auth_password_label': '代理认证密码',\n        'progress_bar_label': '切换进度',\n        'proxy_settings_title': '代理设置',\n        'config_save_success': '配置保存成功',\n        'config_save_failed': '配置保存失败：{}',\n        'config_restart_required': '配置已更改，需要重启服务器生效',\n        'confirm_restart_service': '是否立即重启服务器？',\n        'service_status': '服务状态',\n        'running': '运行中',\n        'stopped': '已停止',\n        'restarting': '重启中',\n        'unknown': '未知',\n        'service_start_failed': '服务启动失败：{}',\n        'service_stop_failed': '服务停止失败：{}',\n        'service_restart_failed': '服务重启失败：{}',\n        'invalid_token': '无效的访问令牌',\n        'config_file_changed': '检测到配置文件更改，正在重新加载...',\n        'proxy_file_changed': '代理文件已更改，正在重新加载...',\n        'test_target_label': '测试目标地址',\n        'invalid_test_target': '无效的测试目标地址',\n        'users_save_success': '用户保存成功',\n        'users_save_failed': '用户保存失败：{}',\n        'user_management_title': '用户管理',\n        'username_column': '用户名',\n        'password_column': '密码',\n        'actions_column': '操作',\n        'add_user_btn': '添加用户',\n        'enter_username': '请输入用户名',\n        'enter_password': '请输入密码',\n        'confirm_delete_user': '确定要删除该用户吗？',\n        'no_logs_found': '未找到匹配的日志',\n        'clear_search': '清除搜索',\n        'web_panel_url': '网页控制面板地址: {}',\n        'web_panel_notice': '请使用浏览器访问上述地址来管理代理服务器',\n        'api_proxy_settings_title': 'API代理设置',\n        'all_retries_failed': '所有重试均已失败，最后错误: {}',\n        'proxy_get_failed': '获取代理失败',\n        'proxy_get_error': '获取代理错误: {}',\n        'request_error': '请求错误: {}',\n        'proxy_switch_error': '代理切换错误: {}',\n    },\n    'en': {\n        'getting_new_proxy': 'Getting new proxy IP',\n        'new_proxy_is': 'New proxy IP is: {}',\n        'proxy_check_start': 'Starting proxy check...',\n        'proxy_check_disabled': 'Proxy check is disabled',\n        'valid_proxies': 'Valid proxies: {}',\n        'no_valid_proxies': 'No valid proxies found',\n        'proxy_check_failed': '{} proxy {} check failed: {}',\n        'proxy_switch': 'Switch proxy: {} -> {}',\n        'proxy_consecutive_fails': 'Proxy {} failed {} times consecutively, switching to new proxy',\n        'proxy_invalid': 'Proxy {} is invalid, switching proxy immediately',\n        'proxy_failure': 'Proxy check failed: {}',\n        'proxy_failure_threshold': 'Proxy invalid, switching now',\n        'proxy_check_error': 'Error occurred during proxy check: {}',\n        'connection_timeout': 'Connection timeout',\n        'data_transfer_timeout': 'Data transfer timeout, retrying...',\n        'connection_reset': 'Connection reset',\n        'transfer_cancelled': 'Transfer cancelled',\n        'data_transfer_error': 'Data transfer error: {}',\n        'unsupported_protocol': 'Unsupported protocol request: {}',\n        'client_error': 'Client handling error: {}',\n        'response_write_error': 'Response write error: {}',\n        'server_closing': 'Server is closing...',\n        'program_interrupted': 'Program interrupted by user',\n        'multiple_proxy_fail': 'Multiple attempts to get valid proxy failed, exiting',\n        'current_proxy': 'Current Proxy',\n        'next_switch': 'Next Switch',\n        'seconds': 's',\n        'no_proxies_available': 'No proxies available',\n        'proxy_file_not_found': 'Proxy file not found: {}',\n        'auth_not_set': 'Not set (No authentication required)',\n        'public_account': 'WeChat Public Number',\n        'blog': 'Blog',\n        'proxy_mode': 'Proxy Rotation Mode',\n        'cycle': 'Cycle',\n        'loadbalance': 'Load Balance',\n        'single_round': 'Single Round',\n        'proxy_interval': 'Proxy Change Interval',\n        'default_auth': 'Default Username and Password',\n        'local_http': 'Local Listening Address (HTTP)',\n        'local_socks5': 'Local Listening Address (SOCKS5)',\n        'star_project': 'Star the Project',\n        'client_handle_error': 'Client handling error: {}',\n        'proxy_invalid_switch': 'Proxy invalid, switching proxy',\n        'request_fail_retry': 'Request failed, retrying remaining times: {}',\n        'user_interrupt': 'User interrupted the program',\n        'new_version_found': 'New version available!',\n        'visit_quark': 'Quark Drive: https://pan.quark.cn/s/39b4b5674570',\n        'visit_github': 'GitHub: https://github.com/honmashironeko/ProxyCat',\n        'visit_baidu': 'Baidu Drive: https://pan.baidu.com/s/1C9LVC9aiaQeYFSj_2mWH1w?pwd=13r5',\n        'latest_version': 'You are using the latest version',\n        'version_info_not_found': 'Version information not found',\n        'update_check_error': 'Failed to check for updates: {}',\n        'unauthorized_ip': 'Unauthorized IP attempt: {}',\n        'client_cancelled': 'Client connection cancelled',\n        'socks5_connection_error': 'SOCKS5 connection error: {}',\n        'connect_timeout': 'Connection timeout',\n        'connection_reset': 'Connection reset',\n        'transfer_cancelled': 'Transfer cancelled',\n        'data_transfer_error': 'Data transfer error: {}',\n        'client_request_error': 'Client request handling error: {}',\n        'unsupported_protocol': 'Unsupported protocol: {}',\n        'request_retry': 'Request failed, retrying ({} left)',\n        'request_error': 'Error during request: {}',\n        'response_write_error': 'Error writing response: {}',\n        'consecutive_failures': 'Consecutive proxy failures detected for {}',\n        'invalid_proxy': 'Current proxy is invalid: {}',\n        'whitelist_error': 'Failed to add whitelist: {}',\n        'api_mode_notice': 'Currently in API mode, proxy address will be automatically obtained upon request',\n        'all_retries_failed': 'All retries failed, last error: {}',\n        'proxy_get_failed': 'Failed to get proxy',\n        'proxy_get_error': 'Error getting proxy: {}',\n        'proxy_switch_error': 'Error switching proxy: {}',\n        'server_running': 'Proxy server running at {}:{}',\n        'server_start_error': 'Server startup error: {}',\n        'server_shutting_down': 'Server shutting down...',\n        'client_process_error': 'Client processing error: {}',\n        'request_handling_error': 'Request handling error: {}',\n        'proxy_forward_error': 'Proxy forwarding error: {}',\n        'data_transfer_timeout': '{}data transfer timeout',\n        'status_update_error': 'Status update error',\n        'display_level_notice': 'Current display level: {}',\n        'display_level_desc': '''Display level description:\n0: Only show proxy switch and error messages\n1: Show proxy switch, countdown and error messages\n2: Show all detailed information''',\n        'new_client_connect': 'New client connection - IP: {}, User: {}',\n        'no_auth': 'No authentication',\n        'connection_error': 'Connection handling error: {}',\n        'cleanup_error': 'Cleanup IP error: {}',\n        'port_changed': 'Port changed: {} -> {}, server restart required to take effect',\n        'config_updated': 'Server configuration updated',\n        'load_proxy_file_error': 'Failed to load proxy file: {}',\n        'proxy_check_result': 'Proxy check completed, valid proxies: {}',\n        'no_proxy': 'No proxy',\n        'cycle_mode': 'Cycle mode',\n        'loadbalance_mode': 'Load balance mode',\n        'proxy_check_start': 'Starting proxy check...',\n        'proxy_check_complete': 'Proxy check completed',\n        'proxy_save_success': 'Proxy saved successfully',\n        'proxy_save_failed': 'Failed to save proxy: {}',\n        'ip_list_save_success': 'IP list saved successfully',\n        'ip_list_save_failed': 'Failed to save IP list: {}',\n        'switch_success': 'Proxy switched successfully',\n        'switch_failed': 'Failed to switch proxy: {}',\n        'service_start_success': 'Service started successfully',\n        'service_start_failed': 'Failed to start service',\n        'service_already_running': 'Service is already running',\n        'service_stop_success': 'Service stopped successfully',\n        'service_not_running': 'Service is not running',\n        'service_restart_success': 'Service restarted successfully',\n        'service_restart_failed': 'Failed to restart service',\n        'invalid_action': 'Invalid action',\n        'operation_failed': 'Operation failed: {}',\n        'logs_cleared': 'Logs cleared',\n        'clear_logs_failed': 'Failed to clear logs: {}',\n        'unsupported_language': 'Unsupported language',\n        'language_changed': 'Language changed to {}',\n        'loading': 'Loading...',\n        'get_proxy_failed': 'Failed to get new proxy: {}',\n        'log_level_all': 'All',\n        'log_level_info': 'Info',\n        'log_level_warning': 'Warning',\n        'log_level_error': 'Error',\n        'log_level_critical': 'Critical',\n        'confirm_clear_logs': 'Are you sure you want to clear all logs? This action cannot be undone.',\n        'language_label': 'Language',\n        'chinese': 'Chinese',\n        'english': 'English',\n        'manual_switch_btn': 'Switch Manually',\n        'service_control_title': 'Service Control',\n        'language_switch_success': 'Language switched successfully',\n        'language_switch_failed': 'Failed to switch language',\n        'refresh_failed': 'Failed to refresh data: {}',\n        'auth_username_label': 'Authentication Username',\n        'auth_password_label': 'Authentication Password',\n        'proxy_auth_username_label': 'Proxy Authentication Username',\n        'proxy_auth_password_label': 'Proxy Authentication Password',\n        'progress_bar_label': 'Switch Progress',\n        'proxy_settings_title': 'Proxy Settings',\n        'config_save_success': 'Configuration saved successfully',\n        'config_save_failed': 'Failed to save configuration: {}',\n        'config_restart_required': 'Configuration changed, server restart required to take effect',\n        'confirm_restart_service': 'Restart server now?',\n        'service_status': 'Service Status',\n        'running': 'Running',\n        'stopped': 'Stopped',\n        'restarting': 'Restarting',\n        'unknown': 'Unknown',\n        'service_start_failed': 'Failed to start service: {}',\n        'service_stop_failed': 'Failed to stop service: {}',\n        'service_restart_failed': 'Failed to restart service: {}',\n        'invalid_token': 'Invalid access token',\n        'config_file_changed': 'Config file change detected, reloading...',\n        'proxy_file_changed': 'Proxy file changed, reloading...',\n        'test_target_label': 'Test Target Address',\n        'invalid_test_target': 'Invalid test target address',\n        'users_save_success': 'Users saved successfully',\n        'users_save_failed': 'Failed to save users: {}',\n        'user_management_title': 'User Management',\n        'username_column': 'Username',\n        'password_column': 'Password',\n        'actions_column': 'Actions',\n        'add_user_btn': 'Add User',\n        'enter_username': 'Please enter username',\n        'enter_password': 'Please enter password',\n        'confirm_delete_user': 'Are you sure you want to delete this user?',\n        'no_logs_found': 'No matching logs found',\n        'clear_search': 'Clear Search',\n        'web_panel_url': 'Web panel URL: {}',\n        'web_panel_notice': 'Please use a browser to access the above URL to manage the proxy server',\n        'api_proxy_settings_title': 'API Proxy Settings',\n    }\n}\n\nclass MessageManager:\n    def __init__(self, messages=MESSAGES):\n        self.messages = messages\n        self.default_lang = 'cn'\n\n    def get(self, key, lang='cn', *args):\n        try:\n            return self.messages[lang][key].format(*args) if args else self.messages[lang][key]\n        except KeyError:\n            return self.messages[self.default_lang][key] if key in self.messages[self.default_lang] else key\n\nmessage_manager = MessageManager(MESSAGES)\nget_message = message_manager.get\n\ndef print_banner(config):\n    language = config.get('language', 'cn').lower()\n    has_auth = config.get('username') and config.get('password')\n    auth_info = f\"{config.get('username')}:{config.get('password')}\" if has_auth else get_message('auth_not_set', language)\n    \n    http_addr = f\"http://{auth_info}@127.0.0.1:{config.get('port')}\" if has_auth else f\"http://127.0.0.1:{config.get('port')}\"\n    socks5_addr = f\"socks5://{auth_info}@127.0.0.1:{config.get('port')}\" if has_auth else f\"socks5://127.0.0.1:{config.get('port')}\"\n    \n    banner_info = [\n        (get_message('public_account', language), '樱花庄的本间白猫'),\n        (get_message('blog', language), 'https://y.shironekosan.cn'),\n        (get_message('proxy_mode', language), get_message('cycle', language) if config.get('mode') == 'cycle' else get_message('loadbalance', language) if config.get('mode') == 'loadbalance' else get_message('single_round', language)),\n        (get_message('proxy_interval', language), f\"{config.get('interval')}{get_message('seconds', language)}\"),\n        (get_message('default_auth', language), auth_info),\n        (get_message('local_http', language), http_addr),\n        (get_message('local_socks5', language), socks5_addr),\n        (get_message('star_project', language), 'https://github.com/honmashironeko/ProxyCat'),\n    ]\n    print(f\"{Fore.MAGENTA}{'=' * 55}\")\n    for key, value in banner_info:\n        print(f\"{Fore.YELLOW}{key}: {Fore.GREEN}{value}{Style.RESET_ALL}\")\n    print(f\"{Fore.MAGENTA}{'=' * 55}\\n\")\n\n    display_level = config.get('display_level', '1')\n    if int(display_level) >= 2:\n        print(f\"\\n{Fore.CYAN}{get_message('display_level_desc', language)}{Style.RESET_ALL}\")\n    else:\n        print(f\"\\n{Fore.CYAN}{get_message('display_level_notice', language).format(display_level)}{Style.RESET_ALL}\")\n\nlogo1 = r\"\"\"\n      |\\      _,,,---,,_  by 本间白猫\nZZZzz /,`.-'`'    -.  ;-;;,_\n     |,4-  ) )-,_. ,\\ (  `'-'\n    '---''(_/--'  `-'\\_)  ProxyCat \n\"\"\"\nlogo2 = r\"\"\"\n             *     ,MMM8&&&.            *\n                  MMMM88&&&&&    .\n                 MMMM88&&&&&&&\n     *           MMM88&&&&&&&&\n                 MMM88&&&&&&&&\n                 'MMM88&&&&&&'\n                   'MMM8&&&'      *    \n            /\\/|_    __/\\\\\n           /    -\\  /-   ~\\  .              '\n           \\    =_YT_ =   /\n           /==*(`    `\\ ~ \\         ProxyCat\n          /     \\     /    `\\      by 本间白猫\n          |     |     ) ~   (\n         /       \\   /     ~ \\\\\n         \\       /   \\~     ~/\n  _/\\_/\\_/\\__  _/_/\\_/\\__~__/_/\\_/\\_/\\_/\\_/\\_\n  |  |  |  | ) ) |  |  | ((  |  |  |  |  |  |\n  |  |  |  |( (  |  |  |  \\\\ |  |  |  |  |  |\n  |  |  |  | )_) |  |  |  |))|  |  |  |  |  | \n  |  |  |  |  |  |  |  |  (/ |  |  |  |  |  |\n  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |\n\"\"\"\nlogo3 = r\"\"\"\n        /\\_/\\         _\n       /``   \\       / )\n       |n n   |__   ( (\n      =(Y =.'`   `\\  \\ \\\\\n       {`\"`        \\  ) ) \n       {       /    |/ /\n        \\\\   ,(     / /\nProxyCat) ) /-'\\  ,_.' by 本间白猫\n       (,(,/ ((,,/\n\"\"\"\nlogo4 = r\"\"\"\n                   .-o=o-.\n               ,  /=o=o=o=\\ .--.\n              _|\\|=o=O=o=O=|    \\\\\n          __.'  a`\\=o=o=o=(`\\   /\n          '.   a 4/`|.-\"\"'`\\ \\ ;'`)   .---.\n            \\   .'  /   .--'  |_.'   / .-._)\n by 本间白猫 `)  _.'   /     /`-.__.' /\n  ProxyCat  `'-.____;     /'-.___.-'\n                       `\\\"\"\"`\n\"\"\"\n\nlogos_list = [logo1, logo2, logo3, logo4]\ndef logos():\n    selected_logo = random.choice(logos_list)\n    print(selected_logo)\n\nDEFAULT_CONFIG = {\n    'port': '1080',\n    'mode': 'cycle',\n    'interval': '300',\n    'username': 'neko',\n    'password': '123456',\n    'use_getip': 'False',\n    'proxy_file': 'ip.txt',\n    'check_proxies': 'True',\n    'whitelist_file': '',\n    'blacklist_file': '',\n    'ip_auth_priority': 'whitelist',\n    'language': 'cn'\n}\n\ndef load_config(config_file='config/config.ini'):\n    try:\n        config = ConfigParser()\n        config.read(config_file, encoding='utf-8')\n        \n        if not config.has_section('Server'):\n            config.add_section('Server')\n            for key, value in DEFAULT_CONFIG.items():\n                config.set('Server', key, str(value))\n            with open(config_file, 'w', encoding='utf-8') as f:\n                config.write(f)\n        \n        result = dict(config.items('Server'))\n        \n        if config.has_section('Users'):\n            result['Users'] = dict(config.items('Users'))\n        \n        return result\n    except Exception as e:\n        logging.error(f\"Error loading config: {e}\")\n        return DEFAULT_CONFIG.copy()\n\ndef load_ip_list(file_path):\n    try:\n        config_path = os.path.join('config', os.path.basename(file_path))\n        if os.path.exists(config_path):\n            with open(config_path, 'r', encoding='utf-8') as f:\n                return set(line.strip() for line in f if line.strip())\n    except Exception as e:\n        logging.error(f\"Error loading IP list: {e}\")\n    return set()\n\n_proxy_check_cache = {}\n_proxy_check_ttl = 10\n\ndef parse_proxy(proxy):\n    try:\n        protocol = proxy.split('://')[0]\n        remaining = proxy.split('://')[1]\n        \n        if '@' in remaining:\n            auth, address = remaining.split('@')\n            host, port = address.split(':')\n            return protocol, auth, host, int(port)\n        else:\n            host, port = remaining.split(':')\n            return protocol, None, host, int(port)\n    except Exception:\n        return None, None, None, None\n\nasync def check_http_proxy(proxy, test_url=None):\n    if test_url is None:\n        test_url = 'https://www.baidu.com'\n    protocol, auth, host, port = parse_proxy(proxy)\n    proxies = {}\n    if auth:\n        proxies['http://'] = f'{protocol}://{auth}@{host}:{port}'\n        proxies['https://'] = f'{protocol}://{auth}@{host}:{port}'\n    else:\n        proxies['http://'] = f'{protocol}://{host}:{port}'\n        proxies['https://'] = f'{protocol}://{host}:{port}'\n        \n    try:\n        async with httpx.AsyncClient(proxies=proxies, timeout=10, verify=False) as client:\n            try:\n                response = await client.get(test_url)\n                return response.status_code == 200\n            except:\n                if test_url.startswith('https://'):\n                    http_url = 'http://' + test_url[8:]\n                    response = await client.get(http_url)\n                    return response.status_code == 200\n                return False\n    except:\n        return False\n\nasync def check_socks_proxy(proxy, test_url=None):\n    if test_url is None:\n        test_url = 'https://www.baidu.com'\n    protocol, auth, host, port = parse_proxy(proxy)\n    if not all([host, port]):\n        return False\n        \n    try:\n        reader, writer = await asyncio.wait_for(asyncio.open_connection(host, port), timeout=5)\n        \n        if auth:\n            writer.write(b'\\x05\\x02\\x00\\x02')\n        else:\n            writer.write(b'\\x05\\x01\\x00')\n            \n        await writer.drain()\n        \n        auth_method = await asyncio.wait_for(reader.readexactly(2), timeout=5)\n        if auth_method[0] != 0x05:\n            return False\n            \n        if auth_method[1] == 0x02 and auth:\n            username, password = auth.split(':')\n            auth_packet = bytes([0x01, len(username)]) + username.encode() + bytes([len(password)]) + password.encode()\n            writer.write(auth_packet)\n            await writer.drain()\n            \n            auth_response = await asyncio.wait_for(reader.readexactly(2), timeout=5)\n            if auth_response[1] != 0x00:\n                return False\n        \n        from urllib.parse import urlparse\n        domain = urlparse(test_url).netloc if '://' in test_url else test_url\n        domain = domain.encode()\n        \n        writer.write(b'\\x05\\x01\\x00\\x03' + bytes([len(domain)]) + domain + b'\\x00\\x50')\n        await writer.drain()\n        \n        response = await asyncio.wait_for(reader.readexactly(10), timeout=5)\n        writer.close()\n        try:\n            await writer.wait_closed()\n        except:\n            pass\n        \n        return response[1] == 0x00\n        \n    except Exception:\n        return False\n\nasync def check_proxy(proxy, test_url=None):\n    current_time = time.time()\n    cache_key = f\"{proxy}:{test_url}\"\n    \n    if cache_key in _proxy_check_cache:\n        cache_time, is_valid = _proxy_check_cache[cache_key]\n        if current_time - cache_time < _proxy_check_ttl:\n            return is_valid\n            \n    proxy_type = proxy.split('://')[0]\n    check_funcs = {\n        'http': check_http_proxy,\n        'https': check_http_proxy,\n        'socks5': check_socks_proxy\n    }\n    \n    if proxy_type not in check_funcs:\n        return False\n    \n    try:\n        test_url = test_url or 'https://www.baidu.com'\n        is_valid = await check_funcs[proxy_type](proxy, test_url)\n        _proxy_check_cache[cache_key] = (current_time, is_valid)\n        return is_valid\n    except Exception:\n        _proxy_check_cache[cache_key] = (current_time, False)\n        return False\n\nasync def check_proxies(proxies, test_url=None):\n    valid_proxies = []\n    for proxy in proxies:\n        if await check_proxy(proxy, test_url):\n            valid_proxies.append(proxy)\n    return valid_proxies\n\nasync def check_for_updates(language='cn'):\n    try:\n        async with httpx.AsyncClient() as client:\n            response = await asyncio.wait_for(client.get(\"https://y.shironekosan.cn/1.html\"), timeout=10)\n            response.raise_for_status()\n            content = response.text\n            match = re.search(r'<p>(ProxyCat-V\\d+\\.\\d+\\.\\d+)</p>', content)\n            if match:\n                latest_version = match.group(1)\n                CURRENT_VERSION = \"ProxyCat-V2.0.4\"\n                if version.parse(latest_version.split('-V')[1]) > version.parse(CURRENT_VERSION.split('-V')[1]):\n                    print(f\"{Fore.YELLOW}{get_message('new_version_found', language)} 当前版本: {CURRENT_VERSION}, 最新版本: {latest_version}{Style.RESET_ALL}\")\n                    print(f\"{Fore.YELLOW}{get_message('visit_quark', language)}{Style.RESET_ALL}\")\n                    print(f\"{Fore.YELLOW}{get_message('visit_github', language)}{Style.RESET_ALL}\")\n                    print(f\"{Fore.YELLOW}{get_message('visit_baidu', language)}{Style.RESET_ALL}\")\n                else:\n                    print(f\"{Fore.GREEN}{get_message('latest_version', language)} ({CURRENT_VERSION}){Style.RESET_ALL}\")\n            else:\n                print(f\"{Fore.RED}{get_message('version_info_not_found', language)}{Style.RESET_ALL}\")\n    except Exception as e:\n        print(f\"{Fore.RED}{get_message('update_check_error', language, e)}{Style.RESET_ALL}\")"
  },
  {
    "path": "modules/proxyserver.py",
    "content": "import asyncio, httpx, logging, re, socket, struct, time, base64, random, os\nfrom modules.modules import get_message, load_ip_list\nfrom asyncio import TimeoutError\nfrom itertools import cycle\nfrom config import getip\nfrom configparser import ConfigParser\n\n\ndef load_proxies(file_path='ip.txt'):\n    with open(file_path, 'r') as file:\n        return [line.strip() for line in file if '://' in line]\n\ndef validate_proxy(proxy):\n    pattern = re.compile(r'^(?P<scheme>socks5|http|https)://(?:(?P<auth>[^@]+)@)?(?P<host>[^:]+):(?P<port>\\d+)$')\n    match = pattern.match(proxy)\n    if not match:\n        return False\n    \n    port = int(match.group('port'))\n    return 0 < port < 65536\n\nclass AsyncProxyServer:\n    def __init__(self, config):\n        self.config = config\n        self._init_config_values(config)\n        self._init_server_state()\n        self._init_connection_settings()\n        self.proxy_failure_lock = asyncio.Lock()\n\n    def _init_config_values(self, config):\n        self.port = int(config.get('port', '1080'))\n        self.mode = config.get('mode', 'cycle')\n        self.interval = int(config.get('interval', '300'))\n        self.language = config.get('language', 'cn')\n        self.use_getip = config.get('use_getip', 'False').lower() == 'true'\n        self.check_proxies = config.get('check_proxies', 'True').lower() == 'true'\n        \n        self.username = config.get('username', '')\n        self.password = config.get('password', '')\n        self.proxy_username = config.get('proxy_username', '')\n        self.proxy_password = config.get('proxy_password', '')\n        \n        self.users = {}\n        if 'Users' in config:\n            self.users = dict(config['Users'].items())\n        self.auth_required = bool(self.users)\n        \n        self.proxy_file = os.path.join('config', os.path.basename(config.get('proxy_file', 'ip.txt')))\n        self.whitelist_file = os.path.join('config', os.path.basename(config.get('whitelist_file', 'whitelist.txt')))\n        self.blacklist_file = os.path.join('config', os.path.basename(config.get('blacklist_file', 'blacklist.txt')))\n        self.ip_auth_priority = config.get('ip_auth_priority', 'whitelist')\n        \n        self.test_url = config.get('test_url', 'https://www.baidu.com')\n        self.whitelist = load_ip_list(self.whitelist_file)\n        self.blacklist = load_ip_list(self.blacklist_file)\n        \n        if self.use_getip:\n            self.getip_url = config.get('getip_url', '')\n            \n        self.switching_proxy = False\n        self.last_switch_attempt = 0\n        self.switch_cooldown = 5  \n        self.proxy_check_cache = {}\n        self.last_check_time = {}\n        self.proxy_check_ttl = 60\n        self.check_cooldown = 10\n        self.connected_clients = set()\n        self.last_proxy_failure_time = 0  \n        self.proxy_failure_cooldown = 3  \n\n    def _init_server_state(self):\n        self.running = False\n        self.stop_server = False\n        self.server_instance = None\n        self.tasks = set()\n        self.last_switch_time = time.time()\n        self.proxy_cycle = None\n        self.current_proxy = None\n        self.proxies = []\n        self.known_clients = set()\n        \n        if not self.use_getip:\n            self.proxies = self._load_file_proxies()\n            if self.proxies:\n                self.proxy_cycle = cycle(self.proxies)\n                self.current_proxy = next(self.proxy_cycle)\n\n    def _init_connection_settings(self):\n        self.buffer_size = 8192\n        self.connection_timeout = 30\n        self.read_timeout = 60\n        self.max_concurrent_requests = 1000\n        self.request_semaphore = asyncio.Semaphore(self.max_concurrent_requests)\n        self.connection_pool = {}\n        self.max_pool_size = 500 \n        self.client_pool = {}\n        self.client_pool_lock = asyncio.Lock() \n        self.proxy_pool = {} \n        self.active_connections = set() \n\n    def _update_config_values(self, new_config):\n        self._init_config_values(new_config)\n        self.last_switch_time = time.time()\n        \n        self.last_switch_attempt = 0\n\n    def _handle_mode_change(self):\n        \n        self.last_switch_attempt = 0\n        \n        if self.use_getip:\n            self.proxies = []\n            self.proxy_cycle = None\n            self.current_proxy = None\n            logging.info(get_message('api_mode_notice', self.language))\n        else:\n            logging.info(f\"切换到{'负载均衡' if self.mode == 'loadbalance' else '循环模式'}模式，从 {self.proxy_file} 加载代理列表\")\n            self.proxies = self._load_file_proxies()\n            logging.info(f\"加载到 {len(self.proxies)} 个代理\")\n            \n            if self.proxies:\n                self.proxy_cycle = cycle(self.proxies)\n                self.current_proxy = next(self.proxy_cycle)\n                logging.info(f\"当前使用代理: {self.current_proxy}\")\n                \n                if self.check_proxies and self.mode != 'loadbalance':  \n                    try:\n                        \n                        loop = asyncio.get_event_loop()\n                        if loop.is_running():\n                            asyncio.create_task(self._check_proxies_wrapper())\n                        else:\n                            loop.run_until_complete(self._check_proxies())\n                    except Exception as e:\n                        logging.error(f\"检查代理时出错: {str(e)}\")\n            else:\n                logging.error(f\"从文件 {self.proxy_file} 加载代理失败，请检查文件是否存在且包含有效代理\")\n\n    async def _check_proxies_wrapper(self):\n        \"\"\"包装 _check_proxies 方法，用于在已运行的事件循环中调用\"\"\"\n        await self._check_proxies()\n\n    def _reload_proxies(self):\n        \n        self.last_switch_attempt = 0\n        \n        logging.info(f\"重新加载代理列表文件 {self.proxy_file}\")\n        self.proxies = self._load_file_proxies()\n        logging.info(f\"加载到 {len(self.proxies)} 个代理\")\n        \n        if self.proxies:\n            self.proxy_cycle = cycle(self.proxies)\n            self.current_proxy = next(self.proxy_cycle)\n            logging.info(f\"当前使用代理: {self.current_proxy}\")\n            \n            if self.check_proxies:\n                try:\n                    \n                    loop = asyncio.get_event_loop()\n                    if loop.is_running():\n                        asyncio.create_task(self._check_proxies_wrapper())\n                    else:\n                        loop.run_until_complete(self._check_proxies())\n                except Exception as e:\n                    logging.error(f\"检查代理时出错: {str(e)}\")\n        else:\n            logging.error(f\"从文件 {self.proxy_file} 加载代理失败，请检查文件是否存在且包含有效代理\")\n\n    async def _check_proxies(self):\n        from modules.modules import check_proxies\n        valid_proxies = await check_proxies(self.proxies, test_url=self.test_url)\n        if valid_proxies:\n            self.proxies = valid_proxies\n            self.proxy_cycle = cycle(valid_proxies)\n            self.current_proxy = next(self.proxy_cycle)\n\n    def _load_file_proxies(self):\n        try:\n            proxy_file = os.path.join('config', os.path.basename(self.proxy_file))\n            if os.path.exists(proxy_file):\n                with open(proxy_file, 'r', encoding='utf-8') as f:\n                    proxies = [line.strip() for line in f if line.strip()]\n                return proxies\n            else:\n                logging.error(get_message('proxy_file_not_found', self.language, proxy_file))\n                return []\n        except Exception as e:\n            logging.error(get_message('load_proxy_file_error', self.language, str(e)))\n            return []\n\n    async def start(self):\n        if not self.running:\n            self.stop_server = False\n            self.running = True\n            \n            try:\n                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n                sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024 * 1024)\n                sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024 * 1024)\n                sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)\n                sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)\n                \n                sock.bind(('0.0.0.0', self.port))\n                \n                loop = asyncio.get_event_loop()\n                if hasattr(loop, 'set_default_executor'):\n                    import concurrent.futures\n                    executor = concurrent.futures.ThreadPoolExecutor(max_workers=max(32, os.cpu_count() * 4))\n                    loop.set_default_executor(executor)\n                \n                server = await asyncio.start_server(\n                    self.handle_client,\n                    sock=sock,\n                    backlog=2048,      \n                    limit=32768,       \n                )\n                \n                self.server_instance = server\n                logging.info(get_message('server_running', self.language, '0.0.0.0', self.port))\n                \n                self.tasks.add(asyncio.create_task(self.cleanup_clients()))\n                self.tasks.add(asyncio.create_task(self._cleanup_pool()))\n                self.tasks.add(asyncio.create_task(self.cleanup_disconnected_ips()))\n                \n                if hasattr(os, 'sched_setaffinity'):\n                    try:\n                        os.sched_setaffinity(0, range(os.cpu_count()))\n                    except:\n                        pass\n                \n                async with server:\n                    await server.serve_forever()\n                    \n            except Exception as e:\n                if not self.stop_server:\n                    logging.error(get_message('server_start_error', self.language, str(e)))\n            finally:\n                self.running = False\n                self.server_instance = None\n\n    async def stop(self):\n        if self.running:\n            self.stop_server = True\n            if self.server_instance:\n                self.server_instance.close()\n                await self.server_instance.wait_closed()\n                self.server_instance = None\n            \n            for task in self.tasks:\n                task.cancel()\n            if self.tasks:\n                await asyncio.gather(*self.tasks, return_exceptions=True)\n            self.tasks.clear()\n            \n            self.running = False\n            logging.info(get_message('server_shutting_down', self.language))\n\n    async def get_next_proxy(self):\n        try:\n            current_time = time.time()\n            \n            \n            if self.mode == 'loadbalance' and self.proxies:\n                if not self.switching_proxy:\n                    try:\n                        self.switching_proxy = True\n                        self.last_switch_attempt = current_time  \n                        \n                        if not self.use_getip:\n                            \n                            if not self.proxy_cycle:\n                                self.proxy_cycle = cycle(self.proxies)\n                            self.current_proxy = next(self.proxy_cycle)\n                            logging.info(f\"负载均衡模式选择代理: {self.current_proxy}\")\n                        else:\n                            \n                            await self.get_proxy()\n                    finally:\n                        self.switching_proxy = False\n                return self.current_proxy\n            \n            \n            if self.switching_proxy or (current_time - self.last_switch_attempt < self.switch_cooldown):\n                return self.current_proxy\n            \n            if self.interval > 0 and current_time - self.last_switch_time >= self.interval or \\\n               (self.use_getip and not self.current_proxy):\n                try:\n                    self.switching_proxy = True\n                    self.last_switch_attempt = current_time\n                    old_proxy = self.current_proxy\n                    \n                    await self.get_proxy()\n\n                finally:\n                    self.switching_proxy = False\n            \n            return self.current_proxy\n                    \n        except Exception as e:\n            logging.error(get_message('proxy_switch_error', self.language, str(e)))\n            self.switching_proxy = False\n            return self.current_proxy\n\n    async def _load_getip_proxy(self):\n        valid_proxies = []\n        for _ in range(4):\n            new_ip = getip.newip()\n            if validate_proxy(new_ip):\n                valid_proxies.append(new_ip)\n                break\n        else:\n            logging.error(get_message('multiple_proxy_fail', self.language))\n            exit(1)\n        return valid_proxies[0]\n\n    def time_until_next_switch(self):\n        return float('inf') if self.mode == 'loadbalance' else max(0, self.interval - (time.time() - self.last_switch_time))\n\n    def check_ip_auth(self, ip):\n        try:\n            if not self.whitelist and not self.blacklist:\n                return True\n\n            if self.ip_auth_priority == 'whitelist':\n                if self.whitelist:\n                    if ip in self.whitelist:\n                        return True\n                    return False\n                if self.blacklist:\n                    return ip not in self.blacklist\n                return True\n            else:\n                if ip in self.blacklist:\n                    return False\n                if self.whitelist:\n                    return ip in self.whitelist\n                return True\n        except Exception as e:\n            logging.error(get_message('whitelist_error', self.language, str(e)))\n            return False\n\n    def _authenticate(self, headers):\n        if not self.auth_required:\n            return True\n            \n        auth_header = headers.get('proxy-authorization', '')\n        if not auth_header:\n            return False\n            \n        try:\n            scheme, credentials = auth_header.split()\n            if scheme.lower() != 'basic':\n                return False\n                \n            decoded = base64.b64decode(credentials).decode()\n            username, password = decoded.split(':')\n            \n            if username in self.users and self.users[username] == password:\n                return username, password\n                \n        except Exception:\n            pass\n            \n        return False\n\n    async def _close_connection(self, writer):\n        try:\n            if writer and not writer.is_closing():\n                writer.write_eof()\n                await writer.drain()\n                writer.close()\n                try:\n                    await writer.wait_closed()\n                except Exception:\n                    pass\n        except Exception:\n            pass\n\n    async def handle_client(self, reader, writer):\n        task = asyncio.current_task()\n        self.tasks.add(task)\n        peername = writer.get_extra_info('peername')\n        if peername:\n            self.active_connections.add(peername)\n        try:\n            peername = writer.get_extra_info('peername')\n            if peername:\n                client_ip = peername[0]\n                if not self.check_ip_auth(client_ip):\n                    logging.warning(get_message('unauthorized_ip', self.language, client_ip))\n                    writer.write(b'HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n                    await writer.drain()\n                    return\n                \n            first_byte = await reader.read(1)\n            if not first_byte:\n                return\n                \n            if first_byte == b'\\x05':\n                await self.handle_socks5_connection(reader, writer)\n            else:\n                await self._handle_client_impl(reader, writer, first_byte)\n                \n        except Exception as e:\n            logging.error(get_message('client_handle_error', self.language, e))\n        finally:\n            if peername:\n                self.active_connections.discard(peername)\n            await self._close_connection(writer)\n            self.tasks.remove(task)\n\n    async def _pipe(self, reader, writer):\n        try:\n            while True:\n                try:\n                    data = await reader.read(self.buffer_size)\n                    if not data:\n                        break\n                    try:\n                        writer.write(data)\n                        await writer.drain()\n                    except (ConnectionError, ConnectionResetError):\n                        \n                        await self.handle_proxy_failure()\n                        break\n                except (ConnectionError, ConnectionResetError):\n                    \n                    await self.handle_proxy_failure()\n                    break\n        except asyncio.CancelledError:\n            pass\n        except Exception as e:\n            \n            await self.handle_proxy_failure()\n            pass\n        finally:\n            await self._close_connection(writer)\n\n    def _split_proxy_auth(self, proxy_addr):\n        match = re.match(r'((?P<username>.+?):(?P<password>.+?)@)?(?P<host>.+)', proxy_addr)\n        if match:\n            username = match.group('username')\n            password = match.group('password')\n            host = match.group('host')\n            if username and password:\n                return f\"{username}:{password}\", host\n        return None, proxy_addr\n\n    async def _create_client(self, proxy):\n        proxy_type, proxy_addr = proxy.split('://')\n        proxy_auth = None\n        \n        if '@' in proxy_addr:\n            auth, proxy_addr = proxy_addr.split('@')\n            proxy_auth = auth\n        \n        if proxy_auth:\n            proxy_url = f\"{proxy_type}://{proxy_auth}@{proxy_addr}\"\n        else:\n            proxy_url = f\"{proxy_type}://{proxy_addr}\"\n            \n        import logging as httpx_logging\n        httpx_logging.getLogger(\"httpx\").setLevel(logging.WARNING)\n        httpx_logging.getLogger(\"hpack\").setLevel(logging.WARNING)\n        httpx_logging.getLogger(\"h2\").setLevel(logging.WARNING)\n            \n        return httpx.AsyncClient(\n            proxies={\"all://\": proxy_url},\n            limits=httpx.Limits(\n                max_keepalive_connections=100,\n                max_connections=1000,\n                keepalive_expiry=30\n            ),\n            timeout=30.0,\n            http2=True,\n            verify=False,\n            follow_redirects=True\n        )\n\n    async def _cleanup_connections(self):\n        current_time = time.time()\n        expired_keys = [\n            key for key, client in self.connection_pool.items()\n            if current_time - client._last_used > 30\n        ]\n        for key in expired_keys:\n            client = self.connection_pool.pop(key)\n            await client.aclose()\n\n    async def handle_socks5_connection(self, reader, writer):\n        try:\n            nmethods = ord(await reader.readexactly(1))\n            await reader.readexactly(nmethods)\n\n            writer.write(b'\\x05\\x02' if self.auth_required else b'\\x05\\x00')\n            await writer.drain()\n\n            if self.auth_required:\n                auth_version = await reader.readexactly(1)\n                if auth_version != b'\\x01':\n                    writer.close()\n                    return\n                \n                ulen = ord(await reader.readexactly(1))\n                username = await reader.readexactly(ulen)\n                plen = ord(await reader.readexactly(1))\n                password = await reader.readexactly(plen)\n\n                username = username.decode()\n                password = password.decode()\n                \n                if username in self.users and self.users[username] == password:\n                    peername = writer.get_extra_info('peername')\n                    if peername:\n                        client_ip = peername[0]\n                        client_key = (client_ip, username)\n                        if client_key not in self.known_clients:\n                            self.known_clients.add(client_key)\n                            logging.info(get_message('new_client_connect', self.language, client_ip, f\"{username}:{password}\"))\n                else:\n                    writer.write(b'\\x01\\x01')\n                    await writer.drain()\n                    writer.close()\n                    return\n\n                writer.write(b'\\x01\\x00')\n                await writer.drain()\n\n            version, cmd, _, atyp = struct.unpack('!BBBB', await reader.readexactly(4))\n            if cmd != 1: \n                writer.write(b'\\x05\\x07\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00')\n                await writer.drain()\n                writer.close()\n                return\n\n            if atyp == 1: \n                dst_addr = socket.inet_ntoa(await reader.readexactly(4))\n            elif atyp == 3:\n                addr_len = ord(await reader.readexactly(1))\n                dst_addr = (await reader.readexactly(addr_len)).decode()\n            elif atyp == 4: \n                dst_addr = socket.inet_ntop(socket.AF_INET6, await reader.readexactly(16))\n            else:\n                writer.write(b'\\x05\\x08\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00')\n                await writer.drain()\n                writer.close()\n                return\n\n            dst_port = struct.unpack('!H', await reader.readexactly(2))[0]\n\n            max_retries = 1\n            retry_count = 0\n            last_error = None\n\n            while retry_count < max_retries:\n                try:\n                    proxy = await self.get_next_proxy()\n                    if not proxy:\n                        raise Exception(\"No proxy available\")\n\n                    proxy_type, proxy_addr = proxy.split('://')\n                    proxy_auth, proxy_host_port = self._split_proxy_auth(proxy_addr)\n                    proxy_host, proxy_port = proxy_host_port.split(':')\n                    proxy_port = int(proxy_port)\n\n                    remote_reader, remote_writer = await asyncio.wait_for(\n                        asyncio.open_connection(proxy_host, proxy_port),\n                        timeout=10\n                    )\n\n                    if proxy_type == 'socks5':\n                        await self._initiate_socks5(remote_reader, remote_writer, dst_addr, dst_port)\n                    elif proxy_type in ['http', 'https']:\n                        await self._initiate_http(remote_reader, remote_writer, dst_addr, dst_port, proxy_auth)\n\n                    writer.write(b'\\x05\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00')\n                    await writer.drain()\n\n                    await asyncio.gather(\n                        self._pipe(reader, remote_writer),\n                        self._pipe(remote_reader, writer)\n                    )\n                    \n                    return\n\n                except (asyncio.TimeoutError, ConnectionRefusedError, ConnectionResetError) as e:\n                    last_error = e\n                    logging.warning(get_message('request_retry', self.language, max_retries - retry_count - 1))\n                    await self.handle_proxy_failure()\n                    retry_count += 1\n                    if retry_count < max_retries:\n                        await asyncio.sleep(1)\n                    continue\n                    \n                except Exception as e:\n                    last_error = e\n                    logging.error(get_message('socks5_connection_error', self.language, str(e)))\n                    await self.handle_proxy_failure()\n                    retry_count += 1\n                    if retry_count < max_retries:\n                        await asyncio.sleep(1)\n                    continue\n\n            #if last_error:\n                #logging.error(get_message('all_retries_failed', self.language, str(last_error)))\n            writer.write(b'\\x05\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00')\n            await writer.drain()\n\n        except Exception as e:\n            logging.error(get_message('socks5_connection_error', self.language, str(e)))\n            writer.write(b'\\x05\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00')\n            await writer.drain()\n\n    async def _initiate_socks5(self, remote_reader, remote_writer, dst_addr, dst_port):\n        try:\n            auth = None\n            proxy_type, proxy_addr = self.current_proxy.split('://')\n            if '@' in proxy_addr:\n                auth, _ = proxy_addr.split('@')\n\n            if auth:\n                remote_writer.write(b'\\x05\\x02\\x00\\x02')\n            else:\n                remote_writer.write(b'\\x05\\x01\\x00')\n                \n            await remote_writer.drain()\n            \n            try:\n                auth_method = await asyncio.wait_for(\n                    remote_reader.readexactly(2),\n                    timeout=10\n                )\n                if auth_method[0] != 0x05:\n                    raise Exception(\"Invalid SOCKS5 proxy response\")\n                    \n                if auth_method[1] == 0x02 and auth:\n                    username, password = auth.split(':')\n                    auth_packet = bytes([0x01, len(username)]) + username.encode() + bytes([len(password)]) + password.encode()\n                    remote_writer.write(auth_packet)\n                    await remote_writer.drain()\n                    \n                    auth_response = await asyncio.wait_for(\n                        remote_reader.readexactly(2),\n                        timeout=10\n                    )\n                    if auth_response[1] != 0x00:\n                        raise Exception(\"Authentication failed\")\n\n                if isinstance(dst_addr, str):\n                    remote_writer.write(b'\\x05\\x01\\x00\\x03' + len(dst_addr).to_bytes(1, 'big') + \n                                      dst_addr.encode() + dst_port.to_bytes(2, 'big'))\n                else:\n                    remote_writer.write(b'\\x05\\x01\\x00\\x01' + socket.inet_aton(dst_addr) + \n                                      dst_port.to_bytes(2, 'big'))\n                \n                await remote_writer.drain()\n                \n                response = await asyncio.wait_for(\n                    remote_reader.readexactly(4),\n                    timeout=10\n                )\n                if response[1] != 0x00:\n                    error_codes = {\n                        0x01: \"General failure\",\n                        0x02: \"Connection not allowed\",\n                        0x03: \"Network unreachable\",\n                        0x04: \"Host unreachable\",\n                        0x05: \"Connection refused\",\n                        0x06: \"TTL expired\",\n                        0x07: \"Command not supported\",\n                        0x08: \"Address type not supported\"\n                    }\n                    error_msg = error_codes.get(response[1], f\"Unknown error code {response[1]}\")\n                    raise Exception(f\"Connection failed: {error_msg}\")\n\n                if response[3] == 0x01:\n                    await asyncio.wait_for(\n                        remote_reader.readexactly(6),\n                        timeout=10\n                    )\n                elif response[3] == 0x03:  \n                    domain_len = (await asyncio.wait_for(\n                        remote_reader.readexactly(1),\n                        timeout=10\n                    ))[0]\n                    await asyncio.wait_for(\n                        remote_reader.readexactly(domain_len + 2),\n                        timeout=10\n                    )\n                elif response[3] == 0x04: \n                    await asyncio.wait_for(\n                        remote_reader.readexactly(18),\n                        timeout=10\n                    )\n                else:\n                    raise Exception(f\"Unsupported address type: {response[3]}\")\n                    \n            except asyncio.TimeoutError:\n                raise Exception(\"SOCKS5 proxy response timeout\")\n            except Exception as e:\n                raise Exception(f\"SOCKS5 protocol error: {str(e)}\")\n                \n        except Exception as e:\n            if isinstance(e, asyncio.TimeoutError):\n                raise Exception(\"SOCKS5 connection timeout\")\n            elif \"Connection refused\" in str(e):\n                raise Exception(\"SOCKS5 connection refused\")\n            else:\n                raise Exception(f\"SOCKS5 initialization failed: {str(e)}\")\n\n    async def _initiate_http(self, remote_reader, remote_writer, dst_addr, dst_port, proxy_auth):\n        connect_request = f'CONNECT {dst_addr}:{dst_port} HTTP/1.1\\r\\nHost: {dst_addr}:{dst_port}\\r\\n'\n        if proxy_auth:\n            connect_request += f'Proxy-Authorization: Basic {base64.b64encode(proxy_auth.encode()).decode()}\\r\\n'\n        connect_request += '\\r\\n'\n        remote_writer.write(connect_request.encode())\n        await remote_writer.drain()\n        \n        while True:\n            line = await remote_reader.readline()\n            if line == b'\\r\\n':\n                break\n\n    async def _handle_client_impl(self, reader, writer, first_byte):\n        try:\n            peername = writer.get_extra_info('peername')\n            client_info = f\"{peername[0]}:{peername[1]}\" if peername else \"未知客户端\"\n            \n            if peername:\n                client_ip = peername[0]\n                if not self.check_ip_auth(client_ip):\n                    logging.warning(get_message('unauthorized_ip', self.language, client_ip))\n                    writer.write(b'HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n                    await writer.drain()\n                    return\n\n            request_line = first_byte + await reader.readline()\n            if not request_line:\n                return\n\n            try:\n                method, path, _ = request_line.decode('utf-8', errors='ignore').split()\n            except (ValueError, UnicodeDecodeError) as e:\n                return\n\n            headers = {}\n            while True:\n                line = await reader.readline()\n                if line == b'\\r\\n':\n                    break\n                if line == b'':\n                    return\n                try:\n                    name, value = line.decode('utf-8', errors='ignore').strip().split(': ', 1)\n                    headers[name.lower()] = value\n                except ValueError:\n                    continue\n\n            if self.auth_required:\n                auth_result = self._authenticate(headers)\n                if not auth_result:\n                    writer.write(b'HTTP/1.1 407 Proxy Authentication Required\\r\\nProxy-Authenticate: Basic realm=\"Proxy\"\\r\\n\\r\\n')\n                    await writer.drain()\n                    return\n                elif isinstance(auth_result, tuple):\n                    username, password = auth_result\n                    peername = writer.get_extra_info('peername')\n                    if peername:\n                        client_ip = peername[0]\n                        client_key = (client_ip, username)\n                        if client_key not in self.known_clients:\n                            self.known_clients.add(client_key)\n                            logging.info(get_message('new_client_connect', self.language, client_ip, f\"{username}:{password}\"))\n\n            if method == 'CONNECT':\n                await self._handle_connect(path, reader, writer)\n            else:\n                await self._handle_request(method, path, headers, reader, writer)\n\n        except (ConnectionError, ConnectionResetError, ConnectionAbortedError):\n            return\n        except asyncio.CancelledError:\n            return\n        except Exception as e:\n            if not isinstance(e, (ConnectionError, ConnectionResetError, ConnectionAbortedError, \n                                asyncio.CancelledError, asyncio.TimeoutError)):\n                logging.error(get_message('client_request_error', self.language, str(e)))\n\n    async def _handle_connect(self, path, reader, writer):\n        try:\n            host, port = path.split(':')\n            port = int(port)\n        except ValueError:\n            writer.write(b'HTTP/1.1 400 Bad Request\\r\\n\\r\\n')\n            await writer.drain()\n            return\n\n        max_retries = 1 \n        retry_count = 0\n        last_error = None\n        \n        while retry_count < max_retries:\n            try:\n                proxy = await self.get_next_proxy()\n                if not proxy:\n                    writer.write(b'HTTP/1.1 503 Service Unavailable\\r\\n\\r\\n')\n                    await writer.drain()\n                    return\n\n                try:\n                    proxy_type, proxy_addr = proxy.split('://')\n                    proxy_auth, proxy_host_port = self._split_proxy_auth(proxy_addr)\n                    proxy_host, proxy_port = proxy_host_port.split(':')\n                    proxy_port = int(proxy_port)\n\n                    remote_reader, remote_writer = await asyncio.wait_for(\n                        asyncio.open_connection(proxy_host, proxy_port), \n                        timeout=10\n                    )\n\n                    if proxy_type == 'http':\n                        connect_headers = [f'CONNECT {host}:{port} HTTP/1.1', f'Host: {host}:{port}']\n                        if proxy_auth:\n                            auth_header = f'Proxy-Authorization: Basic {base64.b64encode(proxy_auth.encode()).decode()}'\n                            connect_headers.append(auth_header)\n                        connect_request = '\\r\\n'.join(connect_headers) + '\\r\\n\\r\\n'\n                        remote_writer.write(connect_request.encode())\n                        await remote_writer.drain()\n                        response = await remote_reader.readline()\n                        if not response.startswith(b'HTTP/1.1 200'):\n                            \n                            await self.handle_proxy_failure()\n                            last_error = f\"Bad Gateway: {response.decode('utf-8', errors='ignore')}\"\n                            retry_count += 1\n                            if retry_count < max_retries:\n                                logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                                await asyncio.sleep(1)\n                                continue\n                            raise Exception(\"Bad Gateway\")\n                        while (await remote_reader.readline()) != b'\\r\\n':\n                            pass\n                    elif proxy_type == 'socks5':\n                        remote_writer.write(b'\\x05\\x01\\x00')\n                        await remote_writer.drain()\n                        if (await remote_reader.read(2))[1] == 0:\n                            remote_writer.write(b'\\x05\\x01\\x00\\x03' + len(host).to_bytes(1, 'big') + host.encode() + port.to_bytes(2, 'big'))\n                        await remote_writer.drain()\n                        if (await remote_reader.read(10))[1] != 0:\n                            \n                            await self.handle_proxy_failure()\n                            last_error = \"SOCKS5 connection failed\"\n                            retry_count += 1\n                            if retry_count < max_retries:\n                                logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                                await asyncio.sleep(1)\n                                continue\n                            raise Exception(\"Bad Gateway\")\n                    else:\n                        raise Exception(\"Unsupported proxy type\")\n\n                    writer.write(b'HTTP/1.1 200 Connection Established\\r\\n\\r\\n')\n                    await writer.drain()\n\n                    await asyncio.gather(\n                        self._pipe(reader, remote_writer),\n                        self._pipe(remote_reader, writer)\n                    )\n                    \n                    \n                    return\n                    \n                except asyncio.TimeoutError:\n                    \n                    await self.handle_proxy_failure()\n                    last_error = \"Connection Timeout\"\n                    retry_count += 1\n                    if retry_count < max_retries:\n                        logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                        await asyncio.sleep(1)\n                        continue\n                    logging.error(get_message('connect_timeout', self.language))\n                    writer.write(b'HTTP/1.1 504 Gateway Timeout\\r\\n\\r\\n')\n                    await writer.drain()\n                    return\n                except Exception as e:\n                    \n                    await self.handle_proxy_failure()\n                    last_error = str(e)\n                    retry_count += 1\n                    if retry_count < max_retries:\n                        logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                        await asyncio.sleep(1)\n                        continue\n                    writer.write(b'HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n                    await writer.drain()\n                    return\n                \n            except Exception as e:\n                last_error = str(e)\n                retry_count += 1\n                if retry_count < max_retries:\n                    logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                    await asyncio.sleep(1)\n                    continue\n                writer.write(b'HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n                await writer.drain()\n                return\n                \n        \n        #if last_error:\n            #logging.error(get_message('all_retries_failed', self.language, last_error))\n\n    async def _handle_request(self, method, path, headers, reader, writer):\n        async with self.request_semaphore:\n            max_retries = 1 \n            retry_count = 0\n            last_error = None\n            \n            while retry_count < max_retries:\n                try:\n                    proxy = await self.get_next_proxy()\n                    if not proxy:\n                        writer.write(b'HTTP/1.1 503 Service Unavailable\\r\\n\\r\\n')\n                        await writer.drain()\n                        return\n\n                    try:\n                        client = await self._get_client(proxy)\n                        \n                        proxy_headers = headers.copy()\n                        proxy_type, proxy_addr = proxy.split('://')\n                        if '@' in proxy_addr:\n                            auth, _ = proxy_addr.split('@')\n                            auth_header = f'Basic {base64.b64encode(auth.encode()).decode()}'\n                            proxy_headers['Proxy-Authorization'] = auth_header\n\n                        try:\n                            async with client.stream(\n                                method,\n                                path,\n                                headers=proxy_headers,\n                                content=reader,\n                                timeout=30.0\n                            ) as response:\n                                writer.write(f'HTTP/1.1 {response.status_code} {response.reason_phrase}\\r\\n'.encode())\n                                \n                                for header_name, header_value in response.headers.items():\n                                    if header_name.lower() not in ('transfer-encoding', 'connection'):\n                                        writer.write(f'{header_name}: {header_value}\\r\\n'.encode())\n                                writer.write(b'\\r\\n')\n                                \n                                try:\n                                    async for chunk in response.aiter_bytes(chunk_size=self.buffer_size):\n                                        if not chunk:\n                                            break\n                                        try:\n                                            writer.write(chunk)\n                                            if len(chunk) >= self.buffer_size:\n                                                await writer.drain()\n                                        except (ConnectionError, ConnectionResetError, ConnectionAbortedError):\n                                            return\n                                        except Exception:\n                                            break \n\n                                    await writer.drain()\n                                except (ConnectionError, ConnectionResetError, ConnectionAbortedError):\n                                    return \n                                except Exception:\n                                    pass\n                                    \n                                \n                                return\n\n                        except httpx.RequestError:\n                            \n                            await self.handle_proxy_failure()\n                            last_error = \"Request Error\"\n                            retry_count += 1\n                            if retry_count < max_retries:\n                                logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                                await asyncio.sleep(1)\n                                continue\n                            return \n                        except Exception as e:\n                            if isinstance(e, (ConnectionError, ConnectionResetError, ConnectionAbortedError)):\n                                \n                                await self.handle_proxy_failure()\n                                last_error = str(e)\n                                retry_count += 1\n                                if retry_count < max_retries:\n                                    logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                                    await asyncio.sleep(1)\n                                    continue\n                                return \n                            writer.write(b'HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n                            await writer.drain()\n                            return\n\n\n                    except httpx.HTTPError:\n                        \n                        await self.handle_proxy_failure()\n                        last_error = \"HTTP Error\"\n                        retry_count += 1\n                        if retry_count < max_retries:\n                            logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                            await asyncio.sleep(1)\n                            continue\n                        return \n                    except Exception as e:\n                        if isinstance(e, (ConnectionError, ConnectionResetError, ConnectionAbortedError)):\n                            \n                            await self.handle_proxy_failure()\n                            last_error = str(e)\n                            retry_count += 1\n                            if retry_count < max_retries:\n                                logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                                await asyncio.sleep(1)\n                                continue\n                            return \n                        writer.write(b'HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n                        await writer.drain()\n                        return\n\n\n                except Exception as e:\n                    if isinstance(e, (ConnectionError, ConnectionResetError, ConnectionAbortedError)):\n                        \n                        await self.handle_proxy_failure()\n                        last_error = str(e)\n                        retry_count += 1\n                        if retry_count < max_retries:\n                            logging.warning(get_message('request_retry', self.language, max_retries - retry_count))\n                            await asyncio.sleep(1)\n                            continue\n                        return \n                    if not isinstance(e, (asyncio.CancelledError,)):\n                        logging.error(f\"请求处理错误: {str(e)}\") \n                    try:\n                        writer.write(b'HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n                        await writer.drain()\n                    except:\n                        pass\n                    return\n                    \n            #if last_error:\n                logging.error(get_message('all_retries_failed', self.language, last_error))\n\n    async def _get_client(self, proxy):\n        async with self.client_pool_lock:\n            current_time = time.time()\n            if proxy in self.client_pool:\n                client, last_used = self.client_pool[proxy]\n                if current_time - last_used < 30 and not client.is_closed:\n                    self.client_pool[proxy] = (client, current_time)\n                    return client\n                else:\n                    await client.aclose()\n                    del self.client_pool[proxy]\n\n            try:\n                client = await self._create_client(proxy)\n                if len(self.client_pool) >= self.max_pool_size:\n                    oldest_proxy = min(self.client_pool, key=lambda x: self.client_pool[x][1])\n                    old_client, _ = self.client_pool[oldest_proxy]\n                    await old_client.aclose()\n                    del self.client_pool[oldest_proxy]\n                \n                self.client_pool[proxy] = (client, current_time)\n                return client\n            except Exception as e:\n                logging.error(f\"创建客户端失败: {str(e)}\")\n                raise\n\n    async def handle_proxy_failure(self):\n        \n        if not self.check_proxies:\n            return\n            \n        \n        current_time = time.time()\n        if current_time - self.last_proxy_failure_time < self.proxy_failure_cooldown:\n            return\n            \n        \n        if self.switching_proxy:\n            return\n            \n        try:\n            if not self.proxy_failure_lock.locked():\n                async with self.proxy_failure_lock:\n                    if (current_time - self.last_proxy_failure_time < self.proxy_failure_cooldown or \n                        self.switching_proxy):\n                        return\n                        \n                    \n                    self.last_proxy_failure_time = current_time\n                    \n                    try:\n                        is_valid = await self.check_current_proxy()\n                        \n                        if not is_valid:\n                            #logging.warning(get_message('proxy_failure', self.language, self.current_proxy))\n                            await self.switch_proxy()\n                    except Exception as e:\n                        logging.error(get_message('proxy_check_error', self.language, str(e)))\n        except Exception as e:\n            logging.error(f\"代理失败处理出错: {str(e)}\")\n\n    async def switch_proxy(self):\n        try:\n            current_time = time.time()\n            \n            \n            if current_time - self.last_switch_attempt < self.switch_cooldown:\n                return False\n                \n            \n            if self.switching_proxy:\n                return False\n                \n            self.switching_proxy = True\n            self.last_switch_attempt = current_time\n            old_proxy = self.current_proxy\n            \n            temp_current_proxy = self.current_proxy\n            \n            await self.get_proxy()\n            \n            \n            if temp_current_proxy != self.current_proxy:\n                self._log_proxy_switch(old_proxy, self.current_proxy)\n                \n                self.last_proxy_failure_time = current_time\n                return True\n                \n            return False\n            \n        except Exception as e:\n            logging.error(get_message('proxy_switch_error', self.language, str(e)))\n            return False\n        finally:\n            \n            self.switching_proxy = False\n\n    async def check_current_proxy(self):\n        try:\n            proxy = self.current_proxy\n            if not proxy:\n                return False\n                \n            current_time = time.time()\n\n            \n            if not self.check_proxies:\n                return True\n\n            \n            if proxy in self.last_check_time:\n                if current_time - self.last_check_time[proxy] < self.check_cooldown:\n                    return self.proxy_check_cache.get(proxy, (current_time, True))[1]\n\n            if proxy in self.proxy_check_cache:\n                cache_time, is_valid = self.proxy_check_cache[proxy]\n                if current_time - cache_time < self.proxy_check_ttl:\n                    return is_valid\n\n            self.last_check_time[proxy] = current_time\n            test_url = self.config.get('test_url', 'https://www.baidu.com')\n            \n            \n            try:\n                from modules.modules import check_proxy\n                \n                is_valid = await check_proxy(proxy, test_url)\n                logging.warning(f\"代理检查结果: {proxy} - {'有效' if is_valid else '无效'}\")\n            except Exception as e:\n                logging.error(f\"代理检测错误: {proxy} - {str(e)}\")\n                is_valid = False\n                \n            \n            self.proxy_check_cache[proxy] = (current_time, is_valid)\n            return is_valid\n\n        except Exception as e:\n            logging.error(f\"代理检测异常: {str(e)}\")\n            if 'proxy' in locals():\n                self.proxy_check_cache[proxy] = (current_time, False)\n            return False\n\n    def _clean_proxy_cache(self):\n        current_time = time.time()\n        self.proxy_check_cache = {\n            proxy: (cache_time, is_valid)\n            for proxy, (cache_time, is_valid) in self.proxy_check_cache.items()\n            if current_time - cache_time < self.proxy_check_ttl\n        }\n        self.last_check_time = {\n            proxy: check_time\n            for proxy, check_time in self.last_check_time.items()\n            if current_time - check_time < self.proxy_check_ttl\n        }\n\n    def initialize_proxies(self):\n        if hasattr(self, 'proxies') and self.proxies:\n            self.proxy_cycle = cycle(self.proxies)\n            return\n            \n        if self.use_getip:\n            logging.info(\"API模式，将在请求时动态获取代理\")\n            return\n            \n        try:\n            logging.info(f\"从文件 {self.proxy_file} 加载代理列表\")\n            self.proxies = self._load_file_proxies()\n            logging.info(f\"加载到 {len(self.proxies)} 个代理\")\n            \n            if self.proxies:\n                self.proxy_cycle = cycle(self.proxies)\n                self.current_proxy = next(self.proxy_cycle)\n                logging.info(f\"初始代理: {self.current_proxy}\")\n        except Exception as e:\n            logging.error(f\"初始化代理列表失败: {str(e)}\")\n\n    async def cleanup_disconnected_ips(self):\n        while True:\n            try:\n                active_ips = {addr[0] for addr in self.active_connections}\n                self.connected_clients = active_ips\n            except Exception as e:\n                logging.error(get_message('cleanup_error', self.language, str(e)))\n            await asyncio.sleep(30)\n\n    def is_docker():\n        return os.path.exists('/.dockerenv')\n\n    async def get_proxy_status(self):\n        if self.mode == 'loadbalance':\n            return f\"{get_message('current_proxy', self.language)}: {self.current_proxy}\"\n        else:\n            time_left = self.time_until_next_switch()\n            if time_left == float('inf'):\n                return f\"{get_message('current_proxy', self.language)}: {self.current_proxy}\"\n            else:\n                return f\"{get_message('current_proxy', self.language)}: {self.current_proxy} | {get_message('next_switch', self.language)}: {time_left:.1f}{get_message('seconds', self.language)}\"\n\n\n    async def _get_proxy_connection(self, proxy):\n        if proxy in self.proxy_pool:\n            conn = self.proxy_pool[proxy]\n            if not conn.is_closed:\n                conn._last_used = time.time()\n                return conn\n                \n        proxy_type, proxy_addr = proxy.split('://')\n        if '@' in proxy_addr:\n            auth, addr = proxy_addr.split('@')\n            username, password = auth.split(':')\n        else:\n            username = self.username\n            password = self.password\n            addr = proxy_addr\n            \n        host, port = addr.split(':')\n        port = int(port)\n        \n        if proxy_type in ('socks5', 'socks4'):\n            conn = await self._create_socks_connection(\n                host, port, username, password, \n                proxy_type == 'socks5'\n            )\n        else:\n            conn = await self._create_http_connection(\n                host, port, username, password\n            )\n            \n        if len(self.proxy_pool) < self.max_pool_size:\n            conn._last_used = time.time()\n            self.proxy_pool[proxy] = conn\n            \n        return conn\n        \n    async def _create_socks_connection(self, host, port, username, password, is_socks5):\n        reader, writer = await asyncio.open_connection(\n            host, port, \n            limit=self.buffer_size\n        )\n        \n        if is_socks5:\n            writer.write(b'\\x05\\x02\\x00\\x02' if username else b'\\x05\\x01\\x00')\n            await writer.drain()\n            \n            version, method = await reader.readexactly(2)\n            if version != 5:\n                raise Exception('Invalid SOCKS version')\n                \n            if method == 2 and username:\n                auth = bytes([1, len(username)]) + username.encode() + \\\n                       bytes([len(password)]) + password.encode()\n                writer.write(auth)\n                await writer.drain()\n                \n                auth_version, status = await reader.readexactly(2)\n                if status != 0:\n                    raise Exception('Authentication failed')\n                    \n        return reader, writer\n        \n    async def _create_http_connection(self, host, port, username, password):\n        reader, writer = await asyncio.open_connection(\n            host, port,\n            limit=self.buffer_size\n        )\n        \n        if username:\n            auth = base64.b64encode(f'{username}:{password}'.encode()).decode()\n            writer.write(f'Proxy-Authorization: Basic {auth}\\r\\n'.encode())\n            await writer.drain()\n            \n        return reader, writer\n        \n    async def _cleanup_pool(self):\n        while True:\n            try:\n                \n                def is_expired(conn):\n                    return hasattr(conn, 'is_closed') and conn.is_closed\n                \n                to_remove = []\n                for proxy, conn in list(self.proxy_pool.items()):\n                    if is_expired(conn):\n                        to_remove.append(proxy)\n                \n                for proxy in to_remove:\n                    if proxy in self.proxy_pool:\n                        del self.proxy_pool[proxy]\n            except Exception as e:\n                logging.error(f'连接池清理错误: {e}')\n            await asyncio.sleep(60)\n\n    def _log_proxy_switch(self, old_proxy, new_proxy):\n        if old_proxy != new_proxy:\n            old_proxy = old_proxy if old_proxy else get_message('no_proxy', self.language)\n            new_proxy = new_proxy if new_proxy else get_message('no_proxy', self.language)\n            \n            current_time = time.time()\n            if not hasattr(self, '_last_log_time') or \\\n               not hasattr(self, '_last_log_content') or \\\n               current_time - self._last_log_time > 1 or \\\n               self._last_log_content != f\"{old_proxy} -> {new_proxy}\":\n                logging.info(get_message('proxy_switch', self.language, old_proxy, new_proxy))\n                self._last_log_time = current_time\n                self._last_log_content = f\"{old_proxy} -> {new_proxy}\"\n\n    async def _validate_proxy(self, proxy):\n        if not proxy:\n            return False\n            \n        \n        if not self.check_proxies:\n            return True\n            \n        try:\n            if not validate_proxy(proxy):\n                logging.warning(get_message('proxy_invalid', self.language, proxy))\n                return False\n                \n            proxy_type, proxy_addr = proxy.split('://')\n            proxy_auth, proxy_host_port = self._split_proxy_auth(proxy_addr)\n            proxy_host, proxy_port = proxy_host_port.split(':')\n            proxy_port = int(proxy_port)\n            \n            try:\n                reader, writer = await asyncio.wait_for(\n                    asyncio.open_connection(proxy_host, proxy_port),\n                    timeout=5\n                )\n                writer.close()\n                try:\n                    await writer.wait_closed()\n                except:\n                    pass\n                return True\n            except:\n                return False\n                \n        except Exception as e:\n            logging.error(get_message('proxy_check_failed', self.language, proxy, str(e)))\n            return False\n\n    async def get_proxy(self):\n        try:\n            old_proxy = self.current_proxy\n            temp_current_proxy = self.current_proxy\n            \n            if not self.use_getip and self.proxies:\n                if not self.proxy_cycle:\n                    self.proxy_cycle = cycle(self.proxies)\n                    \n                for _ in range(3):\n                    new_proxy = next(self.proxy_cycle)\n                    if await self._validate_proxy(new_proxy):\n                        self.current_proxy = new_proxy\n                        self.last_switch_time = time.time()\n                        if temp_current_proxy != self.current_proxy:\n                            self._log_proxy_switch(old_proxy, self.current_proxy)\n                        return self.current_proxy\n                        \n                logging.error(get_message('no_valid_proxies', self.language))\n                return self.current_proxy\n            \n            if self.use_getip:\n                try:\n                    new_proxy = await self._load_getip_proxy()\n                    if new_proxy and await self._validate_proxy(new_proxy):\n\n                        self.current_proxy = new_proxy\n                        self.last_switch_time = time.time()\n                        if temp_current_proxy != self.current_proxy:\n                            self._log_proxy_switch(old_proxy, self.current_proxy)\n                        return self.current_proxy\n                    else:\n                        logging.error(get_message('proxy_get_failed', self.language))\n                except Exception as e:\n                    logging.error(get_message('proxy_get_error', self.language, str(e)))\n            \n            return self.current_proxy\n            \n        except Exception as e:\n            logging.error(get_message('proxy_get_error', self.language, str(e)))\n            return self.current_proxy\n\n    async def cleanup_clients(self):\n        while True:\n            try:\n                async with self.client_pool_lock:\n                    current_time = time.time()\n                    expired_proxies = [\n                        proxy for proxy, (_, last_used) in self.client_pool.items()\n                        if current_time - last_used > 30\n                    ]\n                    for proxy in expired_proxies:\n                        client, _ = self.client_pool[proxy]\n                        await client.aclose()\n                        del self.client_pool[proxy]\n            except Exception as e:\n                logging.error(f\"清理客户端池错误: {str(e)}\")\n            await asyncio.sleep(30)\n\n    def get_active_connections(self):\n        active = []\n        for task in self.tasks:\n            if not task.done():\n                try:\n                    coro = task.get_coro()\n                    if coro.__qualname__.startswith('AsyncProxyServer.handle_client'):\n                        writer = coro.cr_frame.f_locals.get('writer')\n                        if writer:\n                            peername = writer.get_extra_info('peername')\n                            if peername:\n                                active.append(peername)\n                except Exception:\n                    continue\n        return active\n"
  },
  {
    "path": "requirements.txt",
    "content": "colorama==0.4.6\nhttpx==0.27.2\nhttpx[http2,socks]==0.27.2\npackaging==24.1\nRequests==2.32.3\ntqdm>=4.65.0\nflask>=2.0.1\nwerkzeug>=2.0.0\nasyncio>=3.4.3\nconfigparser>=5.0.0"
  },
  {
    "path": "web/static/css/animations.css",
    "content": "@keyframes slideIn {\n    from { transform: translateY(-20px); opacity: 0; }\n    to { transform: translateY(0); opacity: 1; }\n}\n\n@keyframes pulse {\n    0% { transform: scale(1); }\n    50% { transform: scale(1.05); }\n    100% { transform: scale(1); }\n}\n\n@keyframes fadeInUp {\n    from {\n        opacity: 0;\n        transform: translateY(20px);\n    }\n    to {\n        opacity: 1; \n        transform: translateY(0);\n    }\n}\n\n.fade-in {\n    animation: fadeInUp 0.3s ease;\n} "
  },
  {
    "path": "web/static/css/base.css",
    "content": ":root {\n    --primary-color: #3498db;\n    --secondary-color: #2ecc71;\n    --warning-color: #f1c40f;\n    --danger-color: #e74c3c;\n    --background-color: #f5f6fa;\n    --text-color: #2c3e50;\n    --border-color: #dcdde1;\n    --card-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\nbody {\n    background-color: var(--background-color);\n    color: var(--text-color);\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n    line-height: 1.6;\n    margin: 0;\n    padding: 20px;\n}\n\n.container {\n    max-width: 1200px;\n    margin: 0 auto;\n    padding: 20px;\n}\n\n.card {\n    background: #fff;\n    border-radius: 12px;\n    box-shadow: var(--card-shadow);\n    margin-bottom: 20px;\n    padding: 20px;\n    transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n\n.card:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);\n}\n\n.section-title {\n    color: var(--text-color);\n    font-size: 1.5rem;\n    font-weight: 600;\n    margin-bottom: 1.5rem;\n    padding-bottom: 0.5rem;\n    border-bottom: 2px solid var(--border-color);\n} "
  },
  {
    "path": "web/static/css/buttons.css",
    "content": ".btn {\n    border: none;\n    border-radius: 8px;\n    padding: 10px 20px;\n    font-weight: 500;\n    cursor: pointer;\n    transition: all 0.2s ease;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n}\n\n.btn i {\n    font-size: 1.1em;\n}\n\n.btn-primary {\n    background-color: var(--primary-color);\n    color: white;\n}\n\n.btn-success {\n    background-color: var(--secondary-color);\n    color: white;\n}\n\n.btn-warning {\n    background-color: var(--warning-color);\n    color: white;\n}\n\n.btn-danger {\n    background-color: var(--danger-color);\n    color: white;\n}\n\n.btn:hover {\n    transform: translateY(-1px);\n    filter: brightness(1.1);\n}\n\n.btn:active {\n    transform: translateY(1px);\n} "
  },
  {
    "path": "web/static/css/dark-mode.css",
    "content": "@media (prefers-color-scheme: dark) {\n    :root {\n        --background-color: #1a1a1a;\n        --text-color: #ffffff;\n        --border-color: #2d2d2d;\n        --card-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);\n    }\n\n    body {\n        background-color: var(--background-color);\n        color: var(--text-color);\n    }\n\n    .card {\n        background: #2d2d2d;\n    }\n\n    .status-card {\n        background: linear-gradient(145deg, #2d2d2d, #252525);\n    }\n\n    .status-card .card-title {\n        color: #e0e0e0;\n    }\n\n    .status-card .card-value {\n        color: #3498db;\n    }\n\n    .status-card .card-footer {\n        color: #888;\n    }\n\n    .btn {\n        border: 1px solid rgba(255, 255, 255, 0.1);\n    }\n\n    input, select, textarea {\n        background-color: #2d2d2d;\n        border-color: #3d3d3d;\n        color: #ffffff;\n    }\n\n    input:focus, select:focus, textarea:focus {\n        border-color: var(--primary-color);\n        box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);\n    }\n\n    .progress {\n        background: rgba(255, 255, 255, 0.1);\n        box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);\n    }\n\n    .progress-bar {\n        background: linear-gradient(45deg, #64b5f6, #4a90e2);\n    }\n\n    .card:hover {\n        box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);\n    }\n} "
  },
  {
    "path": "web/static/css/forms.css",
    "content": ".form-control, .form-select {\n    border-radius: 8px;\n    border: 2px solid #e9ecef;\n    padding: 10px 15px;\n    transition: all 0.3s ease;\n}\n\n.form-control:focus, .form-select:focus {\n    border-color: #4a90e2;\n    box-shadow: 0 0 0 0.2rem rgba(74, 144, 226, 0.25);\n}\n\n.table {\n    background: white;\n    border-radius: 10px;\n    overflow: hidden;\n    box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);\n}\n\n.table thead th {\n    background: #f8f9fa;\n    border-bottom: 2px solid #dee2e6;\n    color: #2c3e50;\n    font-weight: 600;\n    padding: 15px;\n}\n\n.table tbody td {\n    padding: 12px 15px;\n    vertical-align: middle;\n    border-bottom: 1px solid #eee;\n}\n\n@media (prefers-color-scheme: dark) {\n    .form-control, .form-select {\n        background-color: #2d2d2d;\n        color: #ffffff;\n        border-color: #3d3d3d;\n    }\n\n    .table {\n        background-color: #2d2d2d;\n        color: #ffffff;\n    }\n\n    .table thead th {\n        background-color: #3d3d3d;\n        color: #ffffff;\n    }\n}\n\n/* 浮动保存按钮样式 */\n.floating-save-container {\n    position: fixed;\n    bottom: 30px;\n    right: 30px;\n    z-index: 1000;\n}\n\n.floating-save-btn {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    padding: 12px 24px;\n    border-radius: 30px;\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n    transition: all 0.3s ease;\n    background: linear-gradient(135deg, #4a90e2, #357abd);\n}\n\n.floating-save-btn:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n}\n\n.floating-save-btn:active {\n    transform: translateY(1px);\n}\n\n.floating-save-btn i {\n    font-size: 1.1em;\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .floating-save-btn {\n        background: linear-gradient(135deg, #2980b9, #2c3e50);\n    }\n}\n\n/* 移动端适配 */\n@media (max-width: 768px) {\n    .floating-save-container {\n        bottom: 20px;\n        right: 20px;\n    }\n    \n    .floating-save-btn {\n        padding: 10px 20px;\n    }\n}\n\n/* Toast 样式优化 */\n.toast {\n    position: fixed;\n    top: 20px;\n    right: 20px;\n    min-width: 300px;\n    padding: 16px 20px;\n    border-radius: 12px;\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    color: white;\n    transform: translateX(120%);\n    opacity: 0;\n    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n    z-index: 1100;\n    font-weight: 500;\n}\n\n.toast.show {\n    transform: translateX(0);\n    opacity: 1;\n}\n\n/* 优化不同类型的 Toast 样式 */\n.toast-success {\n    background: linear-gradient(135deg, #00b09b, #96c93d);\n    border-left: 4px solid #96c93d;\n}\n\n.toast-error {\n    background: linear-gradient(135deg, #ff5f6d, #ffc371);\n    border-left: 4px solid #ff5f6d;\n}\n\n.toast-info {\n    background: linear-gradient(135deg, #2193b0, #6dd5ed);\n    border-left: 4px solid #2193b0;\n}\n\n.toast-warning {\n    background: linear-gradient(135deg, #f2994a, #f2c94c);\n    border-left: 4px solid #f2994a;\n}\n\n/* Toast 图标样式 */\n.toast i {\n    font-size: 1.4em;\n    width: 24px;\n    height: 24px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n/* Toast 堆叠效果 */\n.toast + .toast {\n    margin-top: 16px;\n}\n\n/* Toast 文本样式 */\n.toast span {\n    flex: 1;\n    font-size: 14px;\n    line-height: 1.4;\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .toast {\n        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n    }\n    \n    .toast-success {\n        background: linear-gradient(135deg, #004d40, #1b5e20);\n        border-left: 4px solid #00c853;\n    }\n    \n    .toast-error {\n        background: linear-gradient(135deg, #b71c1c, #c62828);\n        border-left: 4px solid #ff1744;\n    }\n    \n    .toast-info {\n        background: linear-gradient(135deg, #0d47a1, #1976d2);\n        border-left: 4px solid #2196f3;\n    }\n    \n    .toast-warning {\n        background: linear-gradient(135deg, #e65100, #f57c00);\n        border-left: 4px solid #ffd600;\n    }\n}\n\n/* 移动端适配 */\n@media (max-width: 768px) {\n    .toast {\n        min-width: auto;\n        width: calc(100% - 32px);\n        margin: 0 16px;\n        padding: 12px 16px;\n    }\n    \n    .toast + .toast {\n        margin-top: 12px;\n    }\n} "
  },
  {
    "path": "web/static/css/logs.css",
    "content": ".log-container {\n    height: 500px;\n    position: relative;\n    margin: 1rem 0;\n}\n\n.log-INFO { \n    background-color: #e8f5e9;\n    color: #1b5e20;\n    border-left: 6px solid #2e7d32;\n    font-size: 17px;\n    font-weight: 600; \n}\n\n.log-WARNING {\n    background-color: #fff3e0;\n    color: #e65100;\n    border-left: 6px solid #f57c00;\n    font-size: 17px;\n    font-weight: 700;\n}\n\n.log-ERROR {\n    background-color: #ffebee;\n    color: #b71c1c;\n    border-left: 6px solid #c62828;\n    font-size: 18px;\n    font-weight: 800;\n}\n\n.log-CRITICAL {\n    background-color: #ffebee;\n    color: #7f0000;\n    border-left: 6px solid #b71c1c;\n    font-size: 18px;\n    font-weight: 900;\n    text-transform: uppercase;\n}\n\n.log-message {\n    font-weight: 600;\n    line-height: 1.5;\n}\n\n/* 搜索高亮样式 */\n.log-highlight {\n    background-color: #fff176;\n    color: #000000;\n    padding: 2px 4px;\n    border-radius: 3px;\n    font-weight: bold;\n    margin: 0 2px;\n}\n\n/* 暗色模式下的高亮样式 */\n@media (prefers-color-scheme: dark) {\n    .log-highlight {\n        background-color: #ffd600;\n        color: #000000;\n    }\n    \n    .log-INFO {\n        background-color: rgba(232, 245, 233, 0.1);\n        color: #81c784;\n        border-left-color: #2e7d32;\n    }\n    \n    .log-WARNING {\n        background-color: rgba(255, 243, 224, 0.1);\n        color: #ffb74d;\n        border-left-color: #f57c00;\n    }\n    \n    .log-ERROR {\n        background-color: rgba(255, 235, 238, 0.1);\n        color: #e57373;\n        border-left-color: #c62828;\n    }\n    \n    .log-CRITICAL {\n        background-color: rgba(255, 235, 238, 0.1);\n        color: #ef5350;\n        border-left-color: #b71c1c;\n    }\n}\n\n/* 日志条目样式 */\n.log-entry {\n    padding: 8px 12px;\n    margin-bottom: 4px;\n    border-radius: 4px;\n    transition: background-color 0.2s ease;\n}\n\n.log-entry:hover {\n    filter: brightness(0.95);\n}\n\n.log-time {\n    font-family: monospace;\n    opacity: 0.8;\n}\n\n.log-level {\n    font-weight: bold;\n    margin: 0 4px;\n} "
  },
  {
    "path": "web/static/css/nav-tabs.css",
    "content": ".nav-tabs {\n    border-bottom: 2px solid #dee2e6;\n    margin-bottom: 20px;\n}\n\n.nav-tabs .nav-link {\n    border: none;\n    color: #6c757d;\n    padding: 10px 20px;\n    font-weight: 500;\n    transition: all 0.2s;\n}\n\n.nav-tabs .nav-link:hover {\n    border: none;\n    color: var(--primary-color);\n}\n\n.nav-tabs .nav-link.active {\n    border: none;\n    color: var(--primary-color);\n    position: relative;\n}\n\n.nav-tabs .nav-link.active:after {\n    content: '';\n    position: absolute;\n    bottom: -2px;\n    left: 0;\n    width: 100%;\n    height: 2px;\n    background-color: var(--primary-color);\n} "
  },
  {
    "path": "web/static/css/progress.css",
    "content": ".progress-container {\n    margin-top: auto;\n}\n\n.progress {\n    height: 8px;\n    border-radius: 10px;\n    background: rgba(0, 0, 0, 0.05);\n    overflow: hidden;\n    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n\n.progress-bar {\n    background: linear-gradient(45deg, #4a90e2, #357abd);\n    box-shadow: 0 0 10px rgba(74, 144, 226, 0.5);\n    transition: width 0.3s ease;\n    position: relative;\n    overflow: hidden;\n}\n\n.progress-bar::after {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background: linear-gradient(\n        90deg,\n        rgba(255, 255, 255, 0) 0%,\n        rgba(255, 255, 255, 0.3) 50%,\n        rgba(255, 255, 255, 0) 100%\n    );\n    animation: shimmer 1.5s infinite;\n}\n\n@keyframes shimmer {\n    0% {\n        transform: translateX(-100%);\n    }\n    100% {\n        transform: translateX(100%);\n    }\n} "
  },
  {
    "path": "web/static/css/responsive.css",
    "content": "@media (max-width: 768px) {\n    .status-card {\n        margin-bottom: 20px;\n    }\n\n    .btn {\n        width: 100%;\n        margin-bottom: 10px;\n    }\n\n    .table-responsive {\n        margin-bottom: 20px;\n    }\n\n    .log-container {\n        height: 300px;\n    }\n\n    .log-search input {\n        font-size: 14px;\n        padding: 10px 40px;\n    }\n}\n\n.language-switch {\n    position: fixed;\n    top: 20px;\n    right: 20px;\n    z-index: 1000;\n    background: white;\n    padding: 8px 16px;\n    border-radius: 20px;\n    box-shadow: var(--card-shadow);\n}\n\n@media (max-width: 768px) {\n    .language-switch {\n        top: 10px;\n        right: 10px;\n    }\n} "
  },
  {
    "path": "web/static/css/search.css",
    "content": ".log-search {\n    position: relative;\n    margin-bottom: 20px;\n}\n\n.log-search input {\n    padding: 12px 45px;\n    border-radius: 25px;\n    font-size: 15px;\n    font-weight: 500;\n    border: 2px solid #e9ecef;\n    transition: all 0.3s ease;\n}\n\n.log-search input:focus {\n    border-color: #4a90e2;\n    box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2);\n}\n\n.log-search .clear-search {\n    position: absolute;\n    right: 15px;\n    top: 50%;\n    transform: translateY(-50%);\n    background: none;\n    border: none;\n    color: #6c757d;\n    cursor: pointer;\n    padding: 5px;\n    transition: all 0.2s ease;\n}\n\n.log-search .clear-search:hover {\n    color: #dc3545;\n    transform: translateY(-50%) scale(1.1);\n}\n\n.log-search i {\n    position: absolute;\n    left: 15px;\n    top: 50%;\n    transform: translateY(-50%);\n    color: #6c757d;\n} "
  },
  {
    "path": "web/static/css/service-control.css",
    "content": ".service-control {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n}\n\n.service-control .card-body {\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n}\n\n.service-title {\n    font-size: 1.2rem;\n    font-weight: 600;\n    color: var(--text-color);\n    margin-bottom: 0.5rem;\n    display: flex;\n    align-items: center;\n}\n\n.service-description {\n    font-size: 0.9rem;\n    color: #666;\n    margin-bottom: 1rem;\n}\n\n.service-actions {\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n    gap: 8px;\n    margin-top: auto;\n}\n\n.service-btn {\n    border: none;\n    border-radius: 6px;\n    padding: 8px;\n    font-size: 0.9rem;\n    font-weight: 500;\n    cursor: pointer;\n    transition: all 0.2s ease;\n}\n\n.service-btn .btn-content {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: 6px;\n}\n\n.service-btn:hover {\n    transform: translateY(-1px);\n    filter: brightness(1.1);\n}\n\n.service-btn:active {\n    transform: translateY(1px);\n}\n\n@media (max-width: 768px) {\n    .service-actions {\n        grid-template-columns: 1fr;\n    }\n    \n    .service-btn {\n        padding: 10px;\n    }\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .service-title {\n        color: #e0e0e0;\n    }\n    \n    .service-description {\n        color: #888;\n    }\n} "
  },
  {
    "path": "web/static/css/status-card.css",
    "content": ".status-cards {\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n    gap: 25px;\n    margin-bottom: 30px;\n}\n\n/* 统一卡片基础样式 */\n.card.status-card, .card.service-control {\n    height: 150px;\n    background: linear-gradient(165deg, #ffffff, #f8f9fa);\n    border-radius: 20px;\n    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n    overflow: hidden;\n    position: relative;\n    border: 1px solid rgba(255, 255, 255, 0.8);\n    padding: 15px;\n    margin-bottom: 15px;\n}\n\n.card-body {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    position: relative;\n    z-index: 1;\n    padding: 0;\n}\n\n.card.status-card:hover, .card.service-control:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n}\n\n/* 统一卡片标题样式 */\n.card-title, .service-title {\n    font-size: 1rem;\n    font-weight: 600;\n    color: var(--text-color);\n    margin-bottom: 0.7rem;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n}\n\n.card-title i, .service-title i {\n    font-size: 1.1em;\n    color: var(--primary-color);\n    background: rgba(52, 152, 219, 0.1);\n    padding: 4px;\n    border-radius: 8px;\n}\n\n/* 统一卡片内容样式 */\n.card-text {\n    color: var(--text-color);\n    font-size: 1.1rem;\n    font-weight: 500;\n    margin-bottom: 0.7rem;\n    flex-grow: 1;\n    display: flex;\n    align-items: center;\n}\n\n/* 统一按钮容器样式 */\n.button-container, .service-actions, .progress-container {\n    margin-top: auto;\n}\n\n/* 统一按钮样式 */\n.btn-sm, .service-btn {\n    padding: 6px 10px;\n    height: 32px;\n    font-size: 0.8rem;\n    border-radius: 8px;\n    font-weight: 500;\n    transition: all 0.3s ease;\n    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11);\n    border: none;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    gap: 6px;\n    color: white;\n    white-space: nowrap;\n}\n\n.btn-sm i, .service-btn i {\n    font-size: 1rem;\n}\n\n/* 按钮颜色统一为蓝色 */\n.btn-primary, \n.service-btn.start-btn,\n.service-btn.stop-btn,\n.service-btn.restart-btn {\n    background: linear-gradient(135deg, #3498db, #2980b9);\n}\n\n/* 按钮悬停效果 */\n.btn-primary:hover, \n.service-btn:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 7px 14px rgba(50, 50, 93, 0.18);\n    filter: brightness(1.1);\n}\n\n/* 按钮点击效果 */\n.btn-primary:active, \n.service-btn:active {\n    transform: translateY(1px);\n}\n\n/* 服务控制按钮组样式 */\n.service-actions {\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n    gap: 6px;\n}\n\n/* 进度条样式 */\n.progress {\n    height: 5px;\n    border-radius: 6px;\n    background: rgba(0, 0, 0, 0.05);\n    overflow: hidden;\n}\n\n.progress-bar {\n    background: linear-gradient(90deg, var(--primary-color), #5dade2);\n    box-shadow: 0 0 10px rgba(52, 152, 219, 0.5);\n}\n\n/* 卡片图标样式 */\n.card-icon {\n    position: absolute;\n    top: 15px;\n    right: 15px;\n    font-size: 1.2rem;\n    color: var(--primary-color);\n    opacity: 0.15;\n    transform: scale(2);\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .card.status-card, .card.service-control {\n        background: linear-gradient(165deg, #2d2d2d, #252525);\n        border: 1px solid rgba(255, 255, 255, 0.1);\n    }\n    \n    .card-title, .service-title {\n        color: #e0e0e0;\n    }\n    \n    .card-title i, .service-title i {\n        background: rgba(52, 152, 219, 0.15);\n    }\n    \n    .card-text {\n        color: #ffffff;\n    }\n    \n    .card-icon {\n        color: var(--primary-color);\n        opacity: 0.1;\n    }\n    \n    .progress {\n        background: rgba(255, 255, 255, 0.1);\n    }\n\n    .btn-sm, .service-btn {\n        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);\n    }\n\n    .btn-sm:hover, .service-btn:hover {\n        box-shadow: 0 7px 14px rgba(0, 0, 0, 0.3);\n    }\n}\n\n/* 响应式布局 */\n@media (max-width: 768px) {\n    .status-cards {\n        grid-template-columns: 1fr;\n        gap: 20px;\n    }\n    \n    .card.status-card, .card.service-control {\n        height: auto;\n        min-height: 140px;\n    }\n\n    .service-actions {\n        margin-top: 0.5rem;\n    }\n    \n    .btn-sm, .service-btn {\n        padding: 6px;\n    }\n}\n\n/* 服务控制卡片特殊样式 */\n.service-control .card-body {\n    padding: 0;\n    justify-content: space-between;\n}\n\n.service-control .service-title {\n    margin-bottom: 1rem;\n    font-size: 1.1rem;\n}\n\n.service-actions {\n    display: grid;\n    grid-template-columns: repeat(3, 1fr);\n    gap: 6px;\n    margin-top: auto;\n}\n\n/* 服务控制按钮样式优化 */\n.service-btn {\n    padding: 6px 10px;\n    min-height: 32px;\n    font-size: 0.8rem;\n}\n\n.service-btn .btn-content {\n    white-space: nowrap;\n}\n\n.service-btn i {\n    font-size: 1rem;\n}\n\n/* 添加信息卡片样式 */\n.info-card {\n    background: linear-gradient(165deg, #ffffff, #f8f9fa);\n    border-radius: 20px;\n    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n    overflow: hidden;\n    position: relative;\n    border: 1px solid rgba(255, 255, 255, 0.8);\n    padding: 10px;\n    height: 100%;\n    min-height: 120px;\n}\n\n.info-card:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n}\n\n/* 地址容器样式 */\n.address-container {\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n    margin-top: 0.4rem;\n}\n\n.address-item {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    padding: 4px 8px;\n    background: rgba(255, 255, 255, 0.5);\n    border-radius: 8px;\n    border: 1px solid rgba(0, 0, 0, 0.05);\n    min-height: 28px;\n}\n\n.address-label {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    font-weight: 500;\n    color: var(--text-color);\n    min-width: 80px;\n    font-size: 0.8rem;\n}\n\n.address-label i {\n    color: var(--primary-color);\n    font-size: 1rem;\n}\n\n.address-value {\n    flex-grow: 1;\n    font-family: monospace;\n    font-size: 0.75rem;\n    color: var(--text-color);\n    padding: 2px 4px;\n    background: rgba(0, 0, 0, 0.03);\n    border-radius: 4px;\n}\n\n.copy-btn {\n    background: none;\n    border: none;\n    color: var(--primary-color);\n    padding: 3px;\n    cursor: pointer;\n    transition: all 0.2s ease;\n    border-radius: 6px;\n    font-size: 0.85rem;\n    width: 24px;\n    height: 24px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n.copy-btn:hover {\n    background: rgba(52, 152, 219, 0.1);\n    transform: scale(1.1);\n}\n\n.copy-btn.copy-success {\n    color: #2ecc71;\n    background: rgba(46, 204, 113, 0.1);\n}\n\n/* 关于信息样式 */\n.about-container {\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n    margin-top: 0.4rem;\n}\n\n.about-item {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    padding: 4px 8px;\n    background: rgba(255, 255, 255, 0.5);\n    border-radius: 8px;\n    border: 1px solid rgba(0, 0, 0, 0.05);\n    min-height: 28px;\n}\n\n.about-item i {\n    font-size: 1.1rem;\n    width: 20px;\n}\n\n.about-content {\n    font-size: 0.85rem;\n}\n\n.github-link {\n    color: var(--primary-color);\n    text-decoration: none;\n    display: flex;\n    align-items: center;\n    gap: 4px;\n    font-weight: 500;\n    font-size: 0.85rem;\n}\n\n.github-link:hover {\n    text-decoration: underline;\n}\n\n.wechat-name {\n    font-weight: 500;\n    color: var(--text-color);\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .info-card {\n        background: linear-gradient(165deg, #2d2d2d, #252525);\n        border: 1px solid rgba(255, 255, 255, 0.1);\n    }\n\n    .address-item, .about-item {\n        background: rgba(255, 255, 255, 0.05);\n        border-color: rgba(255, 255, 255, 0.1);\n    }\n\n    .address-value {\n        background: rgba(0, 0, 0, 0.2);\n        color: #ffffff;\n    }\n\n    .copy-btn:hover {\n        background: rgba(52, 152, 219, 0.2);\n    }\n\n    .wechat-name {\n        color: #e0e0e0;\n    }\n}\n\n/* 响应式布局 */\n@media (max-width: 768px) {\n    .info-card {\n        margin-bottom: 12px;\n        min-height: auto;\n        padding: 10px;\n    }\n\n    .address-item, .about-item {\n        padding: 6px 8px;\n    }\n}\n\n/* 卡片标题样式优化 */\n.info-card .card-title {\n    font-size: 0.95rem;\n    margin-bottom: 0.6rem;\n    gap: 6px;\n}\n\n.info-card .card-title i {\n    font-size: 1rem;\n    padding: 4px;\n}\n\n/* 背景图标优化 */\n.info-card .card-icon {\n    font-size: 1.1rem;\n    transform: scale(1.8);\n    top: 12px;\n    right: 12px;\n}\n\n/* 标签页卡片统一样式 */\n.tab-pane .card {\n    background: linear-gradient(165deg, #ffffff, #f8f9fa);\n    border-radius: 20px;\n    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n    transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n    overflow: hidden;\n    position: relative;\n    border: 1px solid rgba(255, 255, 255, 0.8);\n    padding: 20px;\n}\n\n.tab-pane .card:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n}\n\n/* 标签页标题样式 */\n.tab-pane .card-title {\n    font-size: 1.1rem;\n    font-weight: 600;\n    color: var(--text-color);\n    margin-bottom: 1.2rem;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n}\n\n.tab-pane .card-title i {\n    font-size: 1.2em;\n    color: var(--primary-color);\n    background: rgba(52, 152, 219, 0.1);\n    padding: 6px;\n    border-radius: 8px;\n}\n\n/* 标签页背景图标 */\n.tab-pane .card-icon {\n    position: absolute;\n    top: 20px;\n    right: 20px;\n    font-size: 1.5rem;\n    color: var(--primary-color);\n    opacity: 0.15;\n    transform: scale(2.5);\n}\n\n/* 按钮统一样式 */\n.tab-pane .btn {\n    padding: 8px 16px;\n    height: 36px;\n    font-size: 0.85rem;\n    border-radius: 8px;\n    font-weight: 500;\n    transition: all 0.3s ease;\n    box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11);\n    border: none;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    gap: 6px;\n    color: white;\n    white-space: nowrap;\n    background: linear-gradient(135deg, #3498db, #2980b9);\n}\n\n.tab-pane .btn:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 7px 14px rgba(50, 50, 93, 0.18);\n    filter: brightness(1.1);\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .tab-pane .card {\n        background: linear-gradient(165deg, #2d2d2d, #252525);\n        border: 1px solid rgba(255, 255, 255, 0.1);\n    }\n    \n    .tab-pane .card-title {\n        color: #e0e0e0;\n    }\n    \n    .tab-pane .card-title i {\n        background: rgba(52, 152, 219, 0.15);\n    }\n}\n\n/* 代理管理标签页样式 */\n.proxy-management {\n    display: flex;\n    flex-direction: column;\n    gap: 1rem;\n}\n\n.proxy-actions {\n    display: flex;\n    gap: 12px;\n    margin-top: 1rem;\n}\n\n.proxy-actions .btn {\n    padding: 10px 20px;\n    font-size: 0.9rem;\n    display: inline-flex;\n    align-items: center;\n    gap: 8px;\n    min-width: 120px;\n    justify-content: center;\n    border-radius: 8px;\n    transition: all 0.3s ease;\n}\n\n.proxy-actions .btn i {\n    font-size: 1rem;\n}\n\n/* IP名单标签页样式 */\n.ip-lists-container {\n    display: flex;\n    flex-direction: column;\n    gap: 1.5rem;\n}\n\n.ip-lists-row {\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n    gap: 20px;\n    margin-bottom: 1rem;\n}\n\n.ip-list-section {\n    display: flex;\n    flex-direction: column;\n    gap: 0.8rem;\n}\n\n.ip-list-title {\n    font-size: 1rem;\n    font-weight: 600;\n    color: var(--text-color);\n    margin-bottom: 0.5rem;\n}\n\n.ip-auth-settings {\n    margin-top: 1rem;\n    padding-top: 1rem;\n    border-top: 1px solid var(--border-color);\n}\n\n.ip-lists-actions {\n    display: flex;\n    gap: 12px;\n    margin-top: 1rem;\n}\n\n.ip-lists-actions .btn {\n    padding: 10px 20px;\n    font-size: 0.9rem;\n    display: inline-flex;\n    align-items: center;\n    gap: 8px;\n    min-width: 120px;\n    justify-content: center;\n    border-radius: 8px;\n    transition: all 0.3s ease;\n}\n\n/* 按钮悬停效果 */\n.proxy-actions .btn:hover,\n.ip-lists-actions .btn:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .ip-list-title {\n        color: #e0e0e0;\n    }\n    \n    .ip-auth-settings {\n        border-top-color: rgba(255, 255, 255, 0.1);\n    }\n}\n\n/* 代理列表区域样式 */\n.proxy-lists {\n    background: rgba(255, 255, 255, 0.5);\n    border-radius: 12px;\n    padding: 15px;\n    border: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.proxy-list-section {\n    margin-bottom: 1rem;\n}\n\n.proxy-list-header {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    margin-bottom: 8px;\n    font-weight: 500;\n    color: var(--text-color);\n}\n\n.proxy-list-header i {\n    color: var(--primary-color);\n    font-size: 1.1rem;\n}\n\n.proxy-list-help {\n    margin-left: auto;\n    font-size: 0.85rem;\n    color: #666;\n    font-weight: normal;\n}\n\n.proxy-list-section textarea {\n    font-family: monospace;\n    font-size: 0.9rem;\n    resize: vertical;\n    background: rgba(255, 255, 255, 0.8);\n}\n\n.proxy-list-section textarea:focus {\n    background: white;\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .proxy-lists {\n        background: rgba(255, 255, 255, 0.05);\n        border-color: rgba(255, 255, 255, 0.1);\n    }\n    \n    .proxy-list-header {\n        color: #e0e0e0;\n    }\n    \n    .proxy-list-help {\n        color: #888;\n    }\n    \n    .proxy-list-section textarea {\n        background: rgba(0, 0, 0, 0.2);\n        color: #e0e0e0;\n    }\n    \n    .proxy-list-section textarea:focus {\n        background: rgba(0, 0, 0, 0.3);\n    }\n}\n\n/* 地址显示样式 */\n.address-value {\n    font-family: monospace;\n    background: rgba(0, 0, 0, 0.05);\n    padding: 6px 10px;\n    border-radius: 4px;\n    font-size: 0.9rem;\n    color: var(--text-color);\n    flex-grow: 1;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n}\n\n/* 暗色模式适配 */\n@media (prefers-color-scheme: dark) {\n    .address-value {\n        background: rgba(255, 255, 255, 0.1);\n        color: #ffffff;\n    }\n    \n    .copy-btn:hover {\n        background: rgba(52, 152, 219, 0.2);\n    }\n    \n    .copy-btn.copy-success {\n        background: rgba(46, 204, 113, 0.2);\n    }\n}\n\n/* 服务状态样式 */\n.service-status {\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n    margin-bottom: 0.7rem;\n}\n\n.status-indicator {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n}\n\n.status-dot {\n    width: 6px;\n    height: 6px;\n    border-radius: 50%;\n    transition: background-color 0.3s ease;\n}\n\n.status-dot.running {\n    background-color: #2ecc71;\n    box-shadow: 0 0 10px rgba(46, 204, 113, 0.5);\n    animation: pulse 2s infinite;\n}\n\n.status-dot.stopped {\n    background-color: #e74c3c;\n    box-shadow: 0 0 10px rgba(231, 76, 60, 0.5);\n    animation: none;\n}\n\n.status-text {\n    font-weight: 600;\n    color: var(--text-color);\n    transition: color 0.3s ease;\n}\n\n/* 添加脉冲动画 */\n@keyframes pulse {\n    0% { transform: scale(1); opacity: 1; }\n    50% { transform: scale(1.2); opacity: 0.8; }\n    100% { transform: scale(1); opacity: 1; }\n}\n\n/* 服务控制卡片的按钮容器 */\n.status-card .service-actions {\n    display: flex;\n    gap: 8px;\n    justify-content: center;\n    margin-top: auto;\n}\n\n/* 服务控制按钮基础样式 */\n.status-card .service-btn {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    gap: 4px;\n    padding: 6px 12px;\n    border: none;\n    border-radius: 6px;\n    color: white;\n    transition: all 0.3s ease;\n    font-size: 0.85rem;\n    min-width: 70px;\n    height: 32px;\n    background: linear-gradient(135deg, #3498db, #2980b9);\n}\n\n/* 服务控制按钮交互效果 */\n.status-card .service-btn:hover {\n    transform: translateY(-2px);\n    filter: brightness(1.1);\n    box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);\n}\n\n.status-card .service-btn:disabled {\n    opacity: 0.6;\n    cursor: not-allowed;\n    transform: none;\n    background: linear-gradient(135deg, #95a5a6, #7f8c8d);\n}\n\n/* 响应式布局 */\n@media (max-width: 768px) {\n    .status-card .service-actions {\n        flex-direction: column;\n        gap: 8px;\n    }\n    \n    .status-card .service-btn {\n        width: 100%;\n    }\n}\n\n/* 添加过渡效果 */\n.status-text {\n    transition: color 0.3s ease;\n}\n\n/* 优化按钮状态过渡 */\n.service-btn {\n    transition: all 0.3s ease;\n}\n\n.service-btn:disabled {\n    opacity: 0.6;\n    cursor: not-allowed;\n} "
  },
  {
    "path": "web/templates/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title data-i18n=\"page_title\">ProxyCat 控制面板</title>\n    <link href=\"/static/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/font-awesome.min.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/base.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/status-card.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/progress.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/logs.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/dark-mode.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/buttons.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/forms.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/nav-tabs.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/search.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/animations.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/responsive.css\" rel=\"stylesheet\">\n    <link href=\"/static/css/service-control.css\" rel=\"stylesheet\">\n    <style>\n        .btn {\n            border-radius: 6px;\n            padding: 8px 16px;\n            font-weight: 500;\n            transition: all 0.2s;\n        }\n\n        .btn:hover {\n            transform: translateY(-1px);\n        }\n\n        .btn-primary {\n            background-color: var(--primary-color);\n            border-color: var(--primary-color);\n        }\n\n        .btn-success {\n            background-color: var(--secondary-color);\n            border-color: var(--secondary-color);\n        }\n\n        .btn-warning {\n            background-color: var(--warning-color);\n            border-color: var(--warning-color);\n        }\n\n        .btn-danger {\n            background-color: var(--danger-color);\n            border-color: var(--danger-color);\n        }\n\n        .nav-tabs {\n            border-bottom: 2px solid #dee2e6;\n            margin-bottom: 20px;\n        }\n\n        .nav-tabs .nav-link {\n            border: none;\n            color: #6c757d;\n            padding: 10px 20px;\n            font-weight: 500;\n            transition: all 0.2s;\n        }\n\n        .nav-tabs .nav-link:hover {\n            border: none;\n            color: var(--primary-color);\n        }\n\n        .nav-tabs .nav-link.active {\n            border: none;\n            color: var(--primary-color);\n            position: relative;\n        }\n\n        .nav-tabs .nav-link.active:after {\n            content: '';\n            position: absolute;\n            bottom: -2px;\n            left: 0;\n            width: 100%;\n            height: 2px;\n            background-color: var(--primary-color);\n        }\n\n        .log-container {\n            height: 500px;\n            position: relative;\n            margin: 1rem 0;\n        }\n\n        .log-search {\n            position: relative;\n            margin-bottom: 20px;\n        }\n\n        .log-search input {\n            padding: 12px 45px;\n            border-radius: 25px;\n            font-size: 15px;\n            font-weight: 500;\n            border: 2px solid #e9ecef;\n            transition: all 0.3s ease;\n        }\n\n        .log-search input:focus {\n            border-color: #4a90e2;\n            box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2);\n        }\n\n        .log-search .clear-search {\n            position: absolute;\n            right: 10px;\n            top: 50%;\n            transform: translateY(-50%);\n            background: none;\n            border: none;\n            color: #999;\n            cursor: pointer;\n            padding: 5px;\n            display: none;\n        }\n\n        .log-search .clear-search:hover {\n            color: var(--danger-color);\n        }\n\n        @keyframes slideIn {\n            from { transform: translateY(-20px); opacity: 0; }\n            to { transform: translateY(0); opacity: 1; }\n        }\n\n        @keyframes pulse {\n            0% { transform: scale(1); }\n            50% { transform: scale(1.05); }\n            100% { transform: scale(1); }\n        }\n\n        .card {\n            transition: all 0.3s ease;\n            border: none;\n            border-radius: 15px;\n            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n            animation: slideIn 0.5s ease-out;\n        }\n\n        .card:hover {\n            transform: translateY(-5px);\n            box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);\n        }\n\n        .status-card {\n            background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);\n            min-height: 160px;\n            display: flex;\n            flex-direction: column;\n            justify-content: space-between;\n        }\n\n        .status-card .card-title {\n            color: #2c3e50;\n            font-size: 1.1rem;\n            font-weight: 600;\n            margin-bottom: 15px;\n        }\n\n        .status-card .card-text {\n            color: #34495e;\n            font-size: 1.2rem;\n            margin-bottom: 15px;\n        }\n\n        .table {\n            background: white;\n            border-radius: 10px;\n            overflow: hidden;\n            box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);\n        }\n\n        .table thead th {\n            background: #f8f9fa;\n            border-bottom: 2px solid #dee2e6;\n            color: #2c3e50;\n            font-weight: 600;\n            padding: 15px;\n        }\n\n        .table tbody td {\n            padding: 12px 15px;\n            vertical-align: middle;\n            border-bottom: 1px solid #eee;\n        }\n\n        .form-control, .form-select {\n            border-radius: 8px;\n            border: 2px solid #e9ecef;\n            padding: 10px 15px;\n            transition: all 0.3s ease;\n        }\n\n        .form-control:focus, .form-select:focus {\n            border-color: #4a90e2;\n            box-shadow: 0 0 0 0.2rem rgba(74, 144, 226, 0.25);\n        }\n\n        .log-container {\n            height: 500px;\n            background: #fff;\n            border-radius: 10px;\n            padding: 20px;\n            border: 2px solid #e9ecef;\n            overflow-y: auto;\n        }\n\n        .log-entry {\n            padding: 10px 15px;\n            border-radius: 6px;\n            margin-bottom: 10px;\n            font-family: 'Monaco', 'Menlo', monospace;\n            transition: all 0.2s ease;\n        }\n\n        .log-search {\n            position: relative;\n            margin-bottom: 20px;\n        }\n\n        .log-search input {\n            padding-right: 40px;\n            padding-left: 40px;\n            border-radius: 25px;\n        }\n\n        .log-search i {\n            position: absolute;\n            left: 15px;\n            top: 50%;\n            transform: translateY(-50%);\n            color: #6c757d;\n        }\n\n        .log-search .clear-search {\n            position: absolute;\n            right: 15px;\n            top: 50%;\n            transform: translateY(-50%);\n            background: none;\n            border: none;\n            color: #6c757d;\n            cursor: pointer;\n            padding: 5px;\n            transition: all 0.2s ease;\n        }\n\n        .log-search .clear-search:hover {\n            color: #dc3545;\n            transform: translateY(-50%) scale(1.1);\n        }\n\n        @media (max-width: 768px) {\n            .status-card {\n                margin-bottom: 20px;\n            }\n\n            .btn {\n                width: 100%;\n                margin-bottom: 10px;\n            }\n\n            .table-responsive {\n                margin-bottom: 20px;\n            }\n\n            .log-container {\n                height: 300px;\n            }\n        }\n\n        @media (prefers-color-scheme: dark) {\n            body {\n                background-color: #1a1a1a;\n                color: #ffffff;\n            }\n\n            .card {\n                background-color: #2d2d2d;\n                color: #ffffff;\n            }\n\n            .table {\n                background-color: #2d2d2d;\n                color: #ffffff;\n            }\n\n            .table thead th {\n                background-color: #3d3d3d;\n                color: #ffffff;\n            }\n\n            .form-control, .form-select {\n                background-color: #2d2d2d;\n                color: #ffffff;\n                border-color: #3d3d3d;\n            }\n        }\n\n        /* 添加新的动画效果 */\n        @keyframes fadeInUp {\n            from {\n                opacity: 0;\n                transform: translateY(20px);\n            }\n            to {\n                opacity: 1; \n                transform: translateY(0);\n            }\n        }\n\n        /* 改进卡片样式 */\n        .status-card {\n            animation: fadeInUp 0.5s ease;\n            background: linear-gradient(145deg, #ffffff, #f0f0f0);\n            border: none;\n            transition: all 0.3s ease;\n        }\n\n        .status-card:hover {\n            transform: translateY(-5px);\n            box-shadow: 0 8px 25px rgba(0,0,0,0.1);\n        }\n\n        /* 改进按钮样式 */\n        .btn {\n            position: relative;\n            overflow: hidden;\n            z-index: 1;\n        }\n\n        .btn::after {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(255,255,255,0.1);\n            transform: scaleX(0);\n            transform-origin: right;\n            transition: transform 0.3s ease;\n            z-index: -1;\n        }\n\n        .btn:hover::after {\n            transform: scaleX(1);\n            transform-origin: left;\n        }\n\n        /* 改进日志容器样式 */\n        .log-container {\n            background: linear-gradient(to bottom right, #ffffff, #f8f9fa);\n            border: 1px solid rgba(0,0,0,0.1);\n            box-shadow: 0 4px 15px rgba(0,0,0,0.05);\n        }\n\n        .log-entry {\n            padding: 10px 15px;\n            border-radius: 6px;\n            margin-bottom: 10px;\n            font-family: 'Monaco', 'Menlo', monospace;\n        }\n\n        /* 暗色模式优化 */\n        @media (prefers-color-scheme: dark) {\n            body {\n                background: #1a1a1a;\n                color: #e0e0e0;\n            }\n\n            .status-card {\n                background: linear-gradient(145deg, #2d2d2d, #252525);\n            }\n\n            .card {\n                background-color: #2d2d2d;\n                border: 1px solid #3d3d3d;\n            }\n\n            .log-container {\n                background: linear-gradient(to bottom right, #2d2d2d, #252525);\n                border-color: #3d3d3d;\n            }\n\n            .form-control, .form-select {\n                background-color: #2d2d2d;\n                border-color: #3d3d3d;\n                color: #e0e0e0;\n            }\n\n            .table {\n                color: #e0e0e0;\n            }\n\n            .table thead th {\n                background-color: #252525;\n                border-bottom-color: #3d3d3d;\n            }\n        }\n\n        /* 响应式布局优化 */\n        @media (max-width: 768px) {\n            .status-card {\n                margin-bottom: 20px;\n            }\n\n            .btn-group {\n                display: flex;\n                flex-direction: column;\n                gap: 10px;\n            }\n\n            .btn-group .btn {\n                width: 100%;\n                margin: 0;\n            }\n\n            .log-container {\n                height: 350px;\n            }\n        }\n\n        /* 添加卡片阴影过渡效果 */\n        .card {\n            box-shadow: 0 2px 15px rgba(0,0,0,0.05);\n            transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);\n        }\n\n        .card:hover {\n            box-shadow: 0 5px 25px rgba(0,0,0,0.1);\n            transform: translateY(-3px);\n        }\n\n        /* 美化进度条 */\n        .progress {\n            height: 10px;\n            background: linear-gradient(to right, #f0f0f0, #e0e0e0);\n            border-radius: 10px;\n            overflow: hidden;\n        }\n\n        .progress-bar {\n            background: linear-gradient(45deg, #4a90e2, #357abd);\n            box-shadow: 0 0 10px rgba(74, 144, 226, 0.5);\n            transition: width 0.5s ease;\n        }\n\n        /* 添加波纹效果 */\n        .ripple {\n            position: relative;\n            overflow: hidden;\n        }\n\n        .ripple:after {\n            content: \"\";\n            display: block;\n            position: absolute;\n            width: 100%;\n            height: 100%;\n            top: 0;\n            left: 0;\n            pointer-events: none;\n            background-image: radial-gradient(circle, #fff 10%, transparent 10.01%);\n            background-repeat: no-repeat;\n            background-position: 50%;\n            transform: scale(10, 10);\n            opacity: 0;\n            transition: transform .5s, opacity 1s;\n        }\n\n        .ripple:active:after {\n            transform: scale(0, 0);\n            opacity: .3;\n            transition: 0s;\n        }\n\n        /* 美化表单元素 */\n        .form-control, .form-select {\n            border-radius: 8px;\n            border: 2px solid #e9ecef;\n            padding: 12px 15px;\n            transition: all 0.3s ease;\n            font-size: 0.95rem;\n        }\n\n        .form-control:focus, .form-select:focus {\n            border-color: #4a90e2;\n            box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.15);\n        }\n\n        /* 添加标签悬停效果 */\n        .nav-tabs .nav-link {\n            position: relative;\n            transition: all 0.3s ease;\n        }\n\n        .nav-tabs .nav-link:before {\n            content: '';\n            position: absolute;\n            bottom: -2px;\n            left: 0;\n            width: 100%;\n            height: 2px;\n            background-color: #4a90e2;\n            transform: scaleX(0);\n            transition: transform 0.3s ease;\n        }\n\n        .nav-tabs .nav-link:hover:before {\n            transform: scaleX(0.5);\n        }\n\n        .nav-tabs .nav-link.active:before {\n            transform: scaleX(1);\n        }\n\n        /* 添加加载动画 */\n        .loading-overlay {\n            position: fixed;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: rgba(255, 255, 255, 0.8);\n            display: none;\n            justify-content: center;\n            align-items: center;\n            z-index: 9999;\n            backdrop-filter: blur(5px);\n        }\n\n        .loading-spinner {\n            width: 50px;\n            height: 50px;\n            border: 5px solid #f3f3f3;\n            border-top: 5px solid #4a90e2;\n            border-radius: 50%;\n            animation: spin 1s linear infinite;\n        }\n\n        @keyframes spin {\n            0% { transform: rotate(0deg); }\n            100% { transform: rotate(360deg); }\n        }\n\n        /* 美化滚动条 */\n        ::-webkit-scrollbar {\n            width: 10px;\n            height: 10px;\n        }\n\n        ::-webkit-scrollbar-track {\n            background: #f1f1f1;\n            border-radius: 5px;\n        }\n\n        ::-webkit-scrollbar-thumb {\n            background: #888;\n            border-radius: 5px;\n            border: 2px solid #f1f1f1;\n        }\n\n        ::-webkit-scrollbar-thumb:hover {\n            background: #555;\n        }\n\n        /* 暗色模式适配 */\n        @media (prefers-color-scheme: dark) {\n            .loading-overlay {\n                background: rgba(0, 0, 0, 0.8);\n            }\n\n            .loading-spinner {\n                border-color: #333;\n                border-top-color: #4a90e2;\n            }\n\n            ::-webkit-scrollbar-track {\n                background: #2d2d2d;\n            }\n\n            ::-webkit-scrollbar-thumb {\n                background: #666;\n                border-color: #2d2d2d;\n            }\n\n            ::-webkit-scrollbar-thumb:hover {\n                background: #888;\n            }\n\n            .progress {\n                background: linear-gradient(to right, #2d2d2d, #252525);\n            }\n        }\n\n        /* 添加切换动画 */\n        .tab-pane {\n            animation: fadeIn 0.3s ease;\n        }\n\n        @keyframes fadeIn {\n            from { opacity: 0; transform: translateY(10px); }\n            to { opacity: 1; transform: translateY(0); }\n        }\n\n        /* 美化表格 */\n        .table {\n            border-collapse: separate;\n            border-spacing: 0;\n            border-radius: 10px;\n            overflow: hidden;\n        }\n\n        .table th {\n            background: linear-gradient(to bottom, #f8f9fa, #f0f0f0);\n            border: none;\n            padding: 15px;\n            font-weight: 600;\n        }\n\n        .table td {\n            padding: 12px 15px;\n            border-top: 1px solid #dee2e6;\n            transition: all 0.2s ease;\n        }\n\n        .table tr:hover td {\n            background-color: rgba(74, 144, 226, 0.05);\n        }\n\n        /* 添加新的渐变背景效果 */\n        body {\n            background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);\n            min-height: 100vh;\n        }\n\n        /* 优化卡片容器，防止跳动 */\n        .container {\n            padding: 2rem 1rem;\n            min-height: calc(100vh - 4rem);\n        }\n\n        /* 改进标题样式 */\n        .page-title {\n            font-size: 2.5rem;\n            font-weight: 700;\n            background: linear-gradient(45deg, #4a90e2, #357abd);\n            -webkit-background-clip: text;\n            background-clip: text;\n            -webkit-text-fill-color: transparent;\n            text-align: center;\n            margin-bottom: 2rem;\n        }\n\n        /* 优化状态卡片，固定高度防止跳动 */\n        .status-card {\n            min-height: 180px;\n            display: flex;\n            flex-direction: column;\n            justify-content: space-between;\n            padding: 1.5rem;\n            margin-bottom: 1rem;\n            background: linear-gradient(145deg, #ffffff, #f8f9fa);\n        }\n\n        /* 改进按钮组样式 */\n        .btn-group {\n            display: inline-flex;\n            gap: 0.5rem;\n            flex-wrap: wrap;\n        }\n\n        .btn-group .btn {\n            flex: 1;\n            min-width: 120px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            gap: 0.5rem;\n        }\n\n        /* 添加图标悬停效果 */\n        .btn i {\n            transition: transform 0.3s ease;\n        }\n\n        .btn:hover i {\n            transform: scale(1.2);\n        }\n\n        /* 优化表单布局 */\n        .form-group {\n            margin-bottom: 1.5rem;\n            position: relative;\n        }\n\n        .form-label {\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n            display: block;\n        }\n\n        /* 添加输入框图标 */\n        .form-icon {\n            position: absolute;\n            left: 1rem;\n            top: 50%;\n            transform: translateY(-50%);\n            color: #6c757d;\n            pointer-events: none;\n        }\n\n        .form-icon + .form-control {\n            padding-left: 2.5rem;\n        }\n\n        /* 优化日志容器，防止跳动 */\n        .log-container {\n            height: 500px;\n            position: relative;\n            margin: 1rem 0;\n        }\n\n        .log-entry {\n            margin: 0.5rem 0;\n            padding: 1rem;\n            border-radius: 0.5rem;\n            position: relative;\n            overflow: hidden;\n        }\n\n        /* 暗色模式优化 */\n        @media (prefers-color-scheme: dark) {\n            body {\n                background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);\n            }\n\n            .page-title {\n                background: linear-gradient(45deg, #4a90e2, #64b5f6);\n                -webkit-background-clip: text;\n                background-clip: text;\n                -webkit-text-fill-color: transparent;\n            }\n\n            .status-card {\n                background: linear-gradient(145deg, #2d2d2d, #252525);\n            }\n\n            /* 改进暗色模式下的阴影效果 */\n            .card {\n                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);\n            }\n\n            .card:hover {\n                box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);\n            }\n        }\n\n        /* 添加平滑过渡效果 */\n        .smooth-transition {\n            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n        }\n\n        /* 优化表格响应式布局 */\n        .table-responsive {\n            margin: 1rem 0;\n            border-radius: 0.5rem;\n            overflow: hidden;\n            box-shadow: 0 0 15px rgba(0, 0, 0, 0.05);\n        }\n\n        /* 添加卡片内容过渡效果 */\n        .card-content {\n            opacity: 1;\n            transform: translateY(0);\n            transition: opacity 0.3s ease, transform 0.3s ease;\n        }\n\n        .card-content.loading {\n            opacity: 0.6;\n            transform: translateY(10px);\n        }\n\n        /* 优化加载动画容器 */\n        .loading-overlay {\n            background: rgba(255, 255, 255, 0.9);\n            backdrop-filter: blur(4px);\n            opacity: 0;\n            visibility: hidden;\n            transition: opacity 0.3s ease, visibility 0.3s ease;\n        }\n\n        .loading-overlay.show {\n            opacity: 1;\n            visibility: visible;\n        }\n\n        .log-message {\n            font-weight: 600; /* 为日志消息内容添加加粗效果 */\n            line-height: 1.5; /* 增加行高提高可读性 */\n        }\n\n        /* 进度条容器样式 */\n        .progress-container {\n            margin-top: auto; /* 将进度条推到底部 */\n        }\n\n        .progress {\n            height: 8px;\n            border-radius: 4px;\n            background: linear-gradient(to right, #f0f0f0, #e0e0e0);\n        }\n\n        /* 暗色模式适配 */\n        @media (prefers-color-scheme: dark) {\n            .status-card {\n                background: linear-gradient(145deg, #2d2d2d, #252525);\n            }\n\n            .status-card .card-title {\n                color: #e0e0e0;\n            }\n\n            .status-card .card-text {\n                color: #ffffff;\n            }\n\n            .progress {\n                background: linear-gradient(to right, #2d2d2d, #252525);\n            }\n        }\n\n        /* 只保留加载相关的样式 */\n        .loading-overlay {\n            background: rgba(255, 255, 255, 0.9);\n            backdrop-filter: blur(4px);\n            opacity: 0;\n            visibility: hidden;\n            transition: opacity 0.3s ease, visibility 0.3s ease;\n        }\n\n        .loading-overlay.show {\n            opacity: 1;\n            visibility: visible;\n        }\n\n        .card-content {\n            opacity: 1;\n            transform: translateY(0);\n            transition: opacity 0.3s ease, transform 0.3s ease;\n        }\n\n        .card-content.loading {\n            opacity: 0.6;\n            transform: translateY(10px);\n        }\n\n        /* 暗色模式下的加载遮罩层 */\n        @media (prefers-color-scheme: dark) {\n            .loading-overlay {\n                background: rgba(0, 0, 0, 0.7);\n            }\n        }\n\n        /* 服务控制卡片样式 */\n        .service-control {\n            padding: 1.5rem;\n            background: linear-gradient(145deg, #ffffff, #f8f9fa);\n            border-radius: 15px;\n            transition: all 0.3s ease;\n        }\n\n        .service-title {\n            font-size: 1.5rem;\n            font-weight: 600;\n            color: #2c3e50;\n            margin-bottom: 0.5rem;\n            display: flex;\n            align-items: center;\n        }\n\n        .service-description {\n            font-size: 0.95rem;\n            color: #6c757d;\n        }\n\n        .service-actions {\n            display: flex;\n            gap: 1rem;\n            flex-wrap: wrap;\n        }\n\n        .service-btn {\n            position: relative;\n            flex: 1;\n            min-width: 140px;\n            padding: 0.8rem 1.5rem;\n            border: none;\n            border-radius: 12px;\n            font-weight: 500;\n            font-size: 1rem;\n            overflow: hidden;\n            cursor: pointer;\n            transition: all 0.3s ease;\n        }\n\n        .btn-content {\n            position: relative;\n            z-index: 1;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            gap: 0.5rem;\n            color: white;\n        }\n\n        .btn-bg {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            transition: transform 0.3s ease;\n        }\n\n        .start-btn {\n            background: #28a745;\n        }\n\n        .start-btn .btn-bg {\n            background: linear-gradient(45deg, #2ecc71, #27ae60);\n        }\n\n        .stop-btn {\n            background: #dc3545;\n        }\n\n        .stop-btn .btn-bg {\n            background: linear-gradient(45deg, #e74c3c, #c0392b);\n        }\n\n        .restart-btn {\n            background: #ffc107;\n        }\n\n        .restart-btn .btn-bg {\n            background: linear-gradient(45deg, #f1c40f, #f39c12);\n        }\n\n        .service-btn:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 5px 15px rgba(0,0,0,0.1);\n        }\n\n        .service-btn:hover .btn-bg {\n            transform: scale(1.1);\n        }\n\n        .service-btn:active {\n            transform: translateY(1px);\n        }\n\n        /* 添加按钮图标动画 */\n        .service-btn i {\n            transition: transform 0.3s ease;\n        }\n\n        .service-btn:hover i {\n            transform: scale(1.2);\n        }\n\n        /* 暗色模式适配 */\n        @media (prefers-color-scheme: dark) {\n            .service-control {\n                background: linear-gradient(145deg, #2d2d2d, #252525);\n            }\n\n            .service-title {\n                color: #e0e0e0;\n            }\n\n            .service-description {\n                color: #a0a0a0;\n            }\n\n            .start-btn {\n                background: #1a472a;\n            }\n\n            .start-btn .btn-bg {\n                background: linear-gradient(45deg, #2ecc71, #27ae60);\n                opacity: 0.9;\n            }\n\n            .stop-btn {\n                background: #5c1e1e;\n            }\n\n            .stop-btn .btn-bg {\n                background: linear-gradient(45deg, #e74c3c, #c0392b);\n                opacity: 0.9;\n            }\n\n            .restart-btn {\n                background: #5c4d1a;\n            }\n\n            .restart-btn .btn-bg {\n                background: linear-gradient(45deg, #f1c40f, #f39c12);\n                opacity: 0.9;\n            }\n        }\n\n        /* 响应式布局优化 */\n        @media (max-width: 768px) {\n            .service-control {\n                padding: 1rem;\n            }\n\n            .service-actions {\n                margin-top: 1rem;\n                flex-direction: column;\n            }\n\n            .service-btn {\n                width: 100%;\n                margin: 0.5rem 0;\n            }\n        }\n\n        /* 添加加载状态样式 */\n        .service-btn.loading {\n            pointer-events: none;\n            opacity: 0.8;\n        }\n\n        .service-btn.loading i {\n            animation: spin 1s linear infinite;\n        }\n\n        @keyframes spin {\n            0% { transform: rotate(0deg); }\n            100% { transform: rotate(360deg); }\n        }\n\n        /* 添加通知样式 */\n        .notification-container {\n            position: fixed;\n            top: 20px;\n            right: 20px;\n            z-index: 9999;\n        }\n\n        .notification {\n            background: #fff;\n            border-radius: 8px;\n            padding: 15px 25px;\n            margin-bottom: 10px;\n            box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n            display: flex;\n            align-items: center;\n            gap: 10px;\n            animation: slideInRight 0.3s ease-out;\n            max-width: 350px;\n            border-left: 4px solid;\n        }\n\n        .notification.success {\n            border-left-color: #2ecc71;\n        }\n\n        .notification.error {\n            border-left-color: #e74c3c;\n        }\n\n        .notification i {\n            font-size: 20px;\n        }\n\n        .notification.success i {\n            color: #2ecc71;\n        }\n\n        .notification.error i {\n            color: #e74c3c;\n        }\n\n        .notification-message {\n            color: #2c3e50;\n            font-size: 14px;\n            margin: 0;\n        }\n\n        @keyframes slideInRight {\n            from {\n                transform: translateX(100%);\n                opacity: 0;\n            }\n            to {\n                transform: translateX(0);\n                opacity: 1;\n            }\n        }\n\n        @keyframes fadeOut {\n            from {\n                opacity: 1;\n            }\n            to {\n                opacity: 0;\n            }\n        }\n\n        /* 暗色模式下的通知样式 */\n        @media (prefers-color-scheme: dark) {\n            .notification {\n                background: #2d2d2d;\n            }\n            \n            .notification-message {\n                color: #e0e0e0;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div class=\"container mt-4 fade-in\">\n        <div class=\"row\">\n            <div class=\"col-12\">\n                <h1 class=\"text-center mb-4\" data-i18n=\"title\">ProxyCat 控制面板</h1>\n            </div>\n        </div>\n\n        <div class=\"row mb-4\">\n            <!-- 当前代理卡片 -->\n            <div class=\"col-md-4\">\n                <div class=\"card status-card\">\n                    <i class=\"fa fa-exchange card-icon\"></i>\n                    <div class=\"card-body\">\n                        <h5 class=\"card-title\">\n                            <i class=\"fa fa-random\"></i>\n                            <span data-i18n=\"current_proxy_title\">当前代理</span>\n                        </h5>\n                        <p class=\"card-text\" id=\"current-proxy\" data-i18n=\"loading\">加载中...</p>\n                        <div class=\"button-container\">\n                            <button class=\"btn-sm btn-primary\" onclick=\"switchProxy()\" data-i18n=\"manual_switch_btn\">\n                                <i class=\"fa fa-refresh\"></i>\n                                <span>手动切换</span>\n                            </button>\n                        </div>\n                    </div>\n                </div>\n            </div>\n            \n            <!-- 服务控制卡片 -->\n            <div class=\"col-md-4\">\n                <div class=\"card status-card\">\n                    <i class=\"fa fa-server card-icon\"></i>\n                    <div class=\"card-body\">\n                        <h5 class=\"card-title\">\n                            <i class=\"fa fa-power-off\"></i>\n                            <span data-i18n=\"service_control_title\">服务控制</span>\n                        </h5>\n                        <div class=\"service-status\">\n                            <div class=\"status-indicator\">\n                                <span class=\"status-dot\"></span>\n                                <span class=\"status-text\" data-i18n=\"service_running\">运行中</span>\n                            </div>\n                        </div>\n                        <div class=\"service-actions\">\n                            <button class=\"service-btn start-btn\" onclick=\"controlService('start')\">\n                                <i class=\"fa fa-play\"></i>\n                                <span data-i18n=\"start_service\">启动</span>\n                            </button>\n                            <button class=\"service-btn stop-btn\" onclick=\"controlService('stop')\">\n                                <i class=\"fa fa-stop\"></i>\n                                <span data-i18n=\"stop_service\">停止</span>\n                            </button>\n                            <button class=\"service-btn restart-btn\" onclick=\"controlService('restart')\">\n                                <i class=\"fa fa-refresh\"></i>\n                                <span data-i18n=\"restart_service\">重启</span>\n                            </button>\n                        </div>\n                    </div>\n                </div>\n            </div>\n            \n            <!-- 下次切换卡片 -->\n            <div class=\"col-md-4\">\n                <div class=\"card status-card\">\n                    <i class=\"fa fa-clock-o card-icon\"></i>\n                    <div class=\"card-body\">\n                        <h5 class=\"card-title\">\n                            <i class=\"fa fa-refresh\"></i>\n                            <span data-i18n=\"next_switch_title\">下次切换</span>\n                        </h5>\n                        <p class=\"card-text\" id=\"next-switch\" data-i18n=\"loading\">加载中...</p>\n                        <div class=\"progress-container\">\n                            <div class=\"progress\">\n                                <div class=\"progress-bar\" role=\"progressbar\" style=\"width: 0%\" aria-label=\"切换进度\"></div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"row mt-4\">\n            <!-- 本地监听地址卡片 -->\n            <div class=\"col-md-6\">\n                <div class=\"card info-card\">\n                    <i class=\"fa fa-globe card-icon\"></i>\n                    <div class=\"card-body\">\n                        <h5 class=\"card-title\">\n                            <i class=\"fa fa-plug\"></i>\n                            <span data-i18n=\"local_addresses_title\">本地监听地址</span>\n                        </h5>\n                        <div class=\"address-container\">\n                            <div class=\"address-item\">\n                                <div class=\"address-label\">\n                                    <i class=\"fa fa-desktop\"></i>\n                                    <span data-i18n=\"local_http_text\">HTTP 代理</span>\n                                </div>\n                                <div class=\"address-value\" id=\"http-address\">http://127.0.0.1:1080</div>\n                                <button class=\"copy-btn\" onclick=\"copyToClipboard('http-address')\">\n                                    <i class=\"fa fa-copy\"></i>\n                                </button>\n                            </div>\n                            <div class=\"address-item\">\n                                <div class=\"address-label\">\n                                    <i class=\"fa fa-lock\"></i>\n                                    <span data-i18n=\"local_socks5_text\">SOCKS5 代理</span>\n                                </div>\n                                <div class=\"address-value\" id=\"socks5-address\">socks5://127.0.0.1:1080</div>\n                                <button class=\"copy-btn\" onclick=\"copyToClipboard('socks5-address')\">\n                                    <i class=\"fa fa-copy\"></i>\n                                </button>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n\n            <!-- 关于信息卡片 -->\n            <div class=\"col-md-6\">\n                <div class=\"card info-card\">\n                    <i class=\"fa fa-info-circle card-icon\"></i>\n                    <div class=\"card-body\">\n                        <h5 class=\"card-title\">\n                            <i class=\"fa fa-info\"></i>\n                            <span data-i18n=\"about_info_title\">关于信息</span>\n                        </h5>\n                        <div class=\"about-container\">\n                            <div class=\"about-item\">\n                                <i class=\"fa fa-code-fork\"></i>\n                                <div class=\"about-content\">\n                                    <span data-i18n=\"version_text\">当前版本:</span>\n                                    <span id=\"current-version\">-</span>\n                                    <span id=\"version-status\" class=\"ms-2\"></span>\n                                </div>\n                            </div>\n                            <div class=\"about-item\">\n                                <i class=\"fa fa-github\"></i>\n                                <div class=\"about-content d-flex align-items-center\">\n                                    <span data-i18n=\"star_project_text\">开源项目求 Star:</span>\n                                    <a href=\"https://github.com/honmashironeko/ProxyCat\" target=\"_blank\" class=\"github-link ms-1\">ProxyCat</a>\n                                </div>\n                            </div>\n                            <div class=\"about-item\">\n                                <i class=\"fa fa-wechat\"></i>\n                                <div class=\"about-content\">\n                                    <span data-i18n=\"public_account_text\">公众号:</span>\n                                    <span class=\"wechat-name\" data-i18n=\"public_account_name\">樱花庄的本间白猫</span>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <ul class=\"nav nav-tabs\" id=\"myTab\" role=\"tablist\">\n            <li class=\"nav-item\">\n                <a class=\"nav-link active\" data-bs-toggle=\"tab\" href=\"#config\" data-i18n=\"config_tab\">配置设置</a>\n            </li>\n            <li class=\"nav-item\">\n                <a class=\"nav-link\" data-bs-toggle=\"tab\" href=\"#proxies\" data-i18n=\"proxies_tab\">代理管理</a>\n            </li>\n            <li class=\"nav-item\">\n                <a class=\"nav-link\" data-bs-toggle=\"tab\" href=\"#ip-lists\" data-i18n=\"ip_lists_tab\">IP名单</a>\n            </li>\n            <li class=\"nav-item\">\n                <a class=\"nav-link\" data-bs-toggle=\"tab\" href=\"#logs\" data-i18n=\"logs_tab\">运行日志</a>\n            </li>\n        </ul>\n\n        <div class=\"tab-content mt-3\">\n            <!-- 配置设置标签页 -->\n            <div class=\"tab-pane fade show active\" id=\"config\">\n                <div class=\"row\">\n                    <!-- 基本配置卡片 -->\n                    <div class=\"col-md-4\">\n                        <div class=\"card\">\n                            <i class=\"fa fa-cog card-icon\"></i>\n                            <div class=\"card-body\">\n                                <h5 class=\"card-title\">\n                                    <i class=\"fa fa-wrench\"></i>\n                                    <span data-i18n=\"basic_config_title\">基本配置</span>\n                                </h5>\n                                <form id=\"basic-config-form\">\n                                    <div class=\"mb-3\">\n                                        <label class=\"form-label\" data-i18n=\"port_label\">端口</label>\n                                        <input type=\"number\" class=\"form-control\" name=\"port\">\n                                    </div>\n                                    <div class=\"mb-3\">\n                                        <label class=\"form-label\" data-i18n=\"switch_interval_label\">切换间隔(秒)</label>\n                                        <input type=\"number\" class=\"form-control\" name=\"interval\">\n                                    </div>\n                                    <div class=\"mb-3\">\n                                        <label class=\"form-label\" data-i18n=\"run_mode_label\">运行模式</label>\n                                        <select class=\"form-select\" name=\"mode\">\n                                            <option value=\"cycle\" data-i18n=\"cycle_mode\">循环模式</option>\n                                            <option value=\"loadbalance\" data-i18n=\"loadbalance_mode\">负载均衡</option>\n                                        </select>\n                                    </div>\n                                    <div class=\"mb-3\">\n                                        <label class=\"form-label\" data-i18n=\"language_label\">语言</label>\n                                        <select class=\"form-select\" name=\"language\">\n                                            <option value=\"cn\" data-i18n=\"chinese\">中文</option>\n                                            <option value=\"en\" data-i18n=\"english\">English</option>\n                                        </select>\n                                    </div>\n                                </form>\n                            </div>\n                        </div>\n                    </div>\n\n                    <!-- API代理设置卡片 -->\n                    <div class=\"col-md-4\">\n                        <div class=\"card\">\n                            <i class=\"fa fa-cloud card-icon\"></i>\n                            <div class=\"card-body\">\n                                <h5 class=\"card-title\">\n                                    <i class=\"fa fa-plug\"></i>\n                                    <span data-i18n=\"api_proxy_settings_title\">API代理设置</span>\n                                </h5>\n                                <form id=\"api-config-form\">\n                                    <div class=\"mb-3\">\n                                        <div class=\"form-check\">\n                                            <input class=\"form-check-input\" type=\"checkbox\" name=\"use_getip\" id=\"use-getip\">\n                                            <label class=\"form-check-label\" for=\"use-getip\" data-i18n=\"use_api_label\">使用API获取代理</label>\n                                        </div>\n                                    </div>\n                                    <div class=\"mb-3\">\n                                        <label class=\"form-label\" data-i18n=\"api_url_label\">API地址</label>\n                                        <input type=\"text\" class=\"form-control\" name=\"getip_url\">\n                                    </div>\n                                    <div class=\"mb-3\">\n                                        <label class=\"form-label\" data-i18n=\"proxy_auth_username_label\">代理认证用户名</label>\n                                        <input type=\"text\" class=\"form-control\" name=\"proxy_username\">\n                                    </div>\n                                    <div class=\"mb-3\">\n                                        <label class=\"form-label\" data-i18n=\"proxy_auth_password_label\">代理认证密码</label>\n                                        <input type=\"text\" class=\"form-control\" name=\"proxy_password\">\n                                    </div>\n                                    <div class=\"mb-3\">\n                                        <div class=\"form-check\">\n                                            <input class=\"form-check-input\" type=\"checkbox\" name=\"check_proxies\" id=\"check-proxies\">\n                                            <label class=\"form-check-label\" for=\"check-proxies\" data-i18n=\"enable_proxy_check_label\">代理有效性检测</label>\n                                        </div>\n                                    </div>\n                                </form>\n                            </div>\n                        </div>\n                    </div>\n\n                    <!-- 用户管理卡片 -->\n                    <div class=\"col-md-4\">\n                        <div class=\"card\">\n                            <i class=\"fa fa-users card-icon\"></i>\n                            <div class=\"card-body\">\n                                <div class=\"d-flex justify-content-between align-items-center mb-3\">\n                                    <h5 class=\"card-title mb-0\" data-i18n=\"user_management_title\">用户管理</h5>\n                                    <button class=\"btn btn-sm btn-primary\" onclick=\"addUser()\" data-i18n=\"add_user_btn\">\n                                        <i class=\"fa fa-plus me-1\"></i>添加用户\n                                    </button>\n                                </div>\n                                <div class=\"table-responsive\">\n                                    <table class=\"table\" id=\"user-table\">\n                                        <thead>\n                                            <tr>\n                                                <th data-i18n=\"username_column\">用户名</th>\n                                                <th data-i18n=\"password_column\">密码</th>\n                                                <th data-i18n=\"actions_column\">操作</th>\n                                            </tr>\n                                        </thead>\n                                        <tbody>\n                                        </tbody>\n                                    </table>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <!-- 保留新的浮动保存按钮 -->\n                <div class=\"floating-save-container\">\n                    <button class=\"btn btn-primary floating-save-btn\" onclick=\"saveAllConfig()\">\n                        <i class=\"fa fa-save\"></i>\n                        <span data-i18n=\"save_config\">保存配置</span>\n                    </button>\n                </div>\n            </div>\n\n            <!-- 代理管理标签页 -->\n            <div class=\"tab-pane fade\" id=\"proxies\">\n                <div class=\"card\">\n                    <i class=\"fa fa-random card-icon\"></i>\n                    <div class=\"card-body\">\n                        <h5 class=\"card-title\">\n                            <i class=\"fa fa-list\"></i>\n                            <span data-i18n=\"proxy_management\">代理管理</span>\n                        </h5>\n                        <div class=\"proxy-management\">\n                            <div class=\"proxy-lists\">\n                                <div class=\"row\">\n                                    <div class=\"col-md-4\">\n                                        <div class=\"proxy-list-section\">\n                                            <div class=\"proxy-list-header\">\n                                                <i class=\"fa fa-globe\"></i>\n                                                <span>HTTP代理</span>\n                                            </div>\n                                            <textarea class=\"form-control\" id=\"http-proxy-list\" rows=\"8\" \n                                                placeholder=\"每行一个地址，格式：ip:port&#10;例如：127.0.0.1:8080\"></textarea>\n                                        </div>\n                                    </div>\n                                    <div class=\"col-md-4\">\n                                        <div class=\"proxy-list-section\">\n                                            <div class=\"proxy-list-header\">\n                                                <i class=\"fa fa-lock\"></i>\n                                                <span>HTTPS代理</span>\n                                            </div>\n                                            <textarea class=\"form-control\" id=\"https-proxy-list\" rows=\"8\"\n                                                placeholder=\"每行一个地址，格式：ip:port&#10;例如：127.0.0.1:8443\"></textarea>\n                                        </div>\n                                    </div>\n                                    <div class=\"col-md-4\">\n                                        <div class=\"proxy-list-section\">\n                                            <div class=\"proxy-list-header\">\n                                                <i class=\"fa fa-shield\"></i>\n                                                <span>SOCKS5代理</span>\n                                            </div>\n                                            <textarea class=\"form-control\" id=\"socks5-proxy-list\" rows=\"8\"\n                                                placeholder=\"每行一个地址，格式：ip:port&#10;例如：127.0.0.1:1080\"></textarea>\n                                        </div>\n                                    </div>\n                                </div>\n                                <div class=\"mt-4\">\n                                    <div class=\"proxy-list-section\">\n                                        <div class=\"proxy-list-header\">\n                                            <i class=\"fa fa-code\"></i>\n                                            <span data-i18n=\"full_proxy\">完整代理地址</span>\n                                            <div class=\"proxy-list-help\" data-i18n=\"proxy_help\">支持完整格式的代理地址，包含协议和认证信息</div>\n                                        </div>\n                                        <textarea class=\"form-control\" id=\"full-proxy-list\" rows=\"4\"\n                                            placeholder=\"每行一个地址，格式：protocol://[username:password@]ip:port&#10;例如：http://user:pass@127.0.0.1:8080\"></textarea>\n                                    </div>\n                                </div>\n                            </div>\n                            <div class=\"mb-3 mt-4\">\n                                <label class=\"form-label\" data-i18n=\"test_target_label\">测试目标地址</label>\n                                <input type=\"text\" class=\"form-control\" id=\"test-target-url\" placeholder=\"https://www.baidu.com\">\n                                <div class=\"form-text\" data-i18n=\"test_url_help\">用于检测代理有效性的目标地址</div>\n                            </div>\n                            <div class=\"proxy-actions\">\n                                <button class=\"btn btn-primary\" onclick=\"saveProxies()\">\n                                    <i class=\"fa fa-save\"></i>\n                                    <span data-i18n=\"save_proxy_btn\">保存代理</span>\n                                </button>\n                                <button class=\"btn btn-warning\" onclick=\"checkProxies()\">\n                                    <i class=\"fa fa-check-circle\"></i>\n                                    <span data-i18n=\"check_proxy_btn\">检测代理</span>\n                                </button>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n\n            <!-- IP名单标签页 -->\n            <div class=\"tab-pane fade\" id=\"ip-lists\">\n                <div class=\"card\">\n                    <i class=\"fa fa-shield card-icon\"></i>\n                    <div class=\"card-body\">\n                        <h5 class=\"card-title\">\n                            <i class=\"fa fa-users\"></i>\n                            <span data-i18n=\"ip_lists\">IP名单</span>\n                        </h5>\n                        <div class=\"ip-lists-container\">\n                            <div class=\"ip-lists-row\">\n                                <div class=\"ip-list-section\">\n                                    <div class=\"ip-list-title\" data-i18n=\"whitelist_title\">白名单</div>\n                                    <textarea class=\"form-control\" id=\"whitelist\" rows=\"10\"></textarea>\n                                </div>\n                                <div class=\"ip-list-section\">\n                                    <div class=\"ip-list-title\" data-i18n=\"blacklist_title\">黑名单</div>\n                                    <textarea class=\"form-control\" id=\"blacklist\" rows=\"10\"></textarea>\n                                </div>\n                            </div>\n                            <div class=\"ip-auth-settings\">\n                                <div class=\"mb-3\">\n                                    <label class=\"form-label\" data-i18n=\"ip_auth_priority_label\">IP认证优先级</label>\n                                    <select class=\"form-select\" name=\"ip_auth_priority\">\n                                        <option value=\"whitelist\" data-i18n=\"whitelist_first\">白名单优先</option>\n                                        <option value=\"blacklist\" data-i18n=\"blacklist_first\">黑名单优先</option>\n                                    </select>\n                                </div>\n                                <div class=\"ip-lists-actions\">\n                                    <button class=\"btn btn-primary\" onclick=\"saveIpLists()\">\n                                        <i class=\"fa fa-save\"></i>\n                                        <span data-i18n=\"save_list_btn\">保存名单</span>\n                                    </button>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n\n            <!-- 运行日志标签页 -->\n            <div class=\"tab-pane fade\" id=\"logs\">\n                <div class=\"card\">\n                    <i class=\"fa fa-file-text card-icon\"></i>\n                    <div class=\"card-body\">\n                        <h5 class=\"card-title\">\n                            <i class=\"fa fa-history\"></i>\n                            <span data-i18n=\"run_logs\">运行日志</span>\n                        </h5>\n                        <div class=\"mb-3\">\n                            <div class=\"d-flex justify-content-between align-items-center mb-3\">\n                                <div class=\"d-flex align-items-center\">\n                                    <label class=\"form-label me-2\" data-i18n=\"log_level_label\">日志级别</label>\n                                    <select class=\"form-select me-3\" id=\"log-level\" onchange=\"updateLogs(true)\">\n                                        <option value=\"ALL\" data-i18n=\"log_level_all\">全部</option>\n                                        <option value=\"INFO\" data-i18n=\"log_level_info\">信息</option>\n                                        <option value=\"WARNING\" data-i18n=\"log_level_warning\">警告</option>\n                                        <option value=\"ERROR\" data-i18n=\"log_level_error\">错误</option>\n                                        <option value=\"CRITICAL\" data-i18n=\"log_level_critical\">严重错误</option>\n                                    </select>\n                                </div>\n                                <div class=\"d-flex align-items-center\">\n                                    <button class=\"btn btn-danger\" onclick=\"clearLogs()\" data-i18n=\"clear_logs_btn\">清除日志</button>\n                                </div>\n                            </div>\n                            <div class=\"log-search\">\n                                <i class=\"fa fa-search\"></i>\n                                <input type=\"text\" id=\"log-search-input\" class=\"form-control\" data-i18n-placeholder=\"search_logs\" placeholder=\"搜索日志...\">\n                                <button type=\"button\" id=\"clear-search\" class=\"clear-search\" style=\"display: none;\">\n                                    <i class=\"fa fa-times\"></i>\n                                </button>\n                            </div>\n                        </div>\n                        <div class=\"log-container\" id=\"log-container\"></div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <div class=\"language-switch\">\n        <button id=\"languageToggle\" class=\"btn btn-outline-secondary\">\n            <span class=\"cn-text\">English</span>\n            <span class=\"en-text\" style=\"display: none;\">中文</span>\n        </button>\n    </div>\n\n    <div class=\"loading-overlay\" style=\"display: none;\">\n        <div class=\"spinner-border text-primary\" role=\"status\">\n            <span class=\"visually-hidden\">加载中...</span>\n        </div>\n    </div>\n\n    <div class=\"notification-container\" id=\"notification-container\"></div>\n\n    <script src=\"/static/js/bootstrap.bundle.min.js\"></script>\n    <script src=\"/static/js/jquery.min.js\"></script>\n    <script>\n        let lastLogId = 0;\n        let searchTimeout = null;\n        const searchInput = document.getElementById('log-search-input');\n        const clearSearchBtn = document.getElementById('clear-search');\n\n        function getToken() {\n            const urlParams = new URLSearchParams(window.location.search);\n            return urlParams.get('token') || '';\n        }\n\n        function appendToken(url) {\n            const token = getToken();\n            const separator = url.includes('?') ? '&' : '?';\n            return token ? `${url}${separator}token=${token}` : url;\n        }\n\n        function updateAddresses(config) {\n            const username = config.username || '';\n            const password = config.password || '';\n            const port = config.port || '1080';\n            \n            let httpAddress = `http://127.0.0.1:${port}`;\n            let socks5Address = `socks5://127.0.0.1:${port}`;\n            \n            if (username && password) {\n                httpAddress = `http://${username}:${password}@127.0.0.1:${port}`;\n                socks5Address = `socks5://${username}:${password}@127.0.0.1:${port}`;\n            }\n            \n            $('#http-address').text(httpAddress);\n            $('#socks5-address').text(socks5Address);\n        }\n\n        function updateStatus() {\n            $.get(appendToken('/api/status'), function(data) {\n                // 只在值发生变化时更新显示\n                const currentProxy = $('#current-proxy').text();\n                let newProxy = data.current_proxy || translations[currentLanguage].no_proxy;\n                \n                // 负载均衡模式特殊处理\n                if (data.mode === 'loadbalance') {\n                    newProxy = translations[currentLanguage].loadbalance_mode || '负载均衡模式';\n                }\n                \n                if (currentProxy !== newProxy) {\n                    $('#current-proxy').text(newProxy);\n                }\n\n                // 更新运行状态文本和样式\n                const statusText = document.querySelector('.status-text');\n                const statusDot = document.querySelector('.status-dot');\n                const status = data.service_status || 'stopped';\n                \n                if (statusText) {\n                    statusText.textContent = translations[currentLanguage].service_status[status];\n                }\n                \n                if (statusDot) {\n                    statusDot.classList.remove('running', 'stopped');\n                    void statusDot.offsetWidth; // 触发重排以重置动画\n                    statusDot.classList.add(status);\n                }\n\n                // 更新进度条和其他状态...\n                if (data.mode === 'loadbalance') {\n                    // 负载均衡模式不显示倒计时\n                    $('#next-switch').text(translations[currentLanguage].loadbalance_mode || '负载均衡模式');\n                    $('.progress-bar').css('width', '100%');\n                } else if (data.interval && data.time_left !== undefined) {\n                    const timeLeft = Math.max(0, Math.ceil(data.time_left));\n                    $('#next-switch').text(`${timeLeft} ${translations[currentLanguage].seconds}`);\n                    \n                    // 更新进度条\n                    const progress = ((data.interval - data.time_left) / data.interval) * 100;\n                    $('.progress-bar').css('width', `${Math.min(100, progress)}%`);\n                }\n\n                // 更新运行模式\n                const modeText = translations[currentLanguage].proxy_mode[data.mode];\n                if ($('#proxy-mode').text() !== modeText) {\n                    $('#proxy-mode').text(modeText);\n                }\n\n                // 更新按钮状态\n                updateServiceButtons(status);\n            });\n        }\n\n        function updateCountdown() {\n            $.get(appendToken('/api/status'), function(data) {\n                if (data.time_left !== undefined) {\n                    // 负载均衡模式特殊处理\n                    if (data.time_left === -1 || data.mode === 'loadbalance') {\n                        // 负载均衡模式不显示倒计时\n                        $('.progress-bar').css('width', '100%');\n                        $('#next-switch').text(translations[currentLanguage]['loadbalance_mode'] || '负载均衡模式');\n                    } else if (data.interval) {\n                        const timeLeft = Math.max(0, Math.ceil(data.time_left));\n                        const progress = Math.min(100, ((data.interval - data.time_left) / data.interval) * 100);\n                        \n                        $('.progress-bar').css('width', progress + '%');\n                        $('#next-switch').text(timeLeft + ' ' + translations[currentLanguage]['seconds']);\n                    }\n                }\n            });\n        }\n\n        function loadConfig() {\n            $.get(appendToken('/api/status'), function(data) {\n                if (data.config) {\n                    const config = data.config;\n                    \n                    // 更新表单值 - 始终使用配置文件中的值，避免空值\n                    $('input[name=\"port\"]').val(config.port || '1080');\n                    $('select[name=\"mode\"]').val(config.mode || 'cycle');\n                    $('input[name=\"interval\"]').val(config.interval || '300');\n                    $('input[name=\"username\"]').val(config.username || '');\n                    $('input[name=\"password\"]').val(config.password || '');\n                    // 修正checkbox的状态设置\n                    $('input[name=\"use_getip\"]').prop('checked', (config.use_getip || '').toLowerCase() === 'true');\n                    $('input[name=\"getip_url\"]').val(config.getip_url || '');\n                    $('input[name=\"proxy_username\"]').val(config.proxy_username || '');\n                    $('input[name=\"proxy_password\"]').val(config.proxy_password || '');\n                    $('input[name=\"proxy_file\"]').val(config.proxy_file || 'ip.txt');\n                    $('input[name=\"check_proxies\"]').prop('checked', (config.check_proxies || '').toLowerCase() === 'true');\n                    $('select[name=\"language\"]').val(config.language || 'cn');\n                    $('input[name=\"whitelist_file\"]').val(config.whitelist_file || '');\n                    $('input[name=\"blacklist_file\"]').val(config.blacklist_file || '');\n                    $('select[name=\"ip_auth_priority\"]').val(config.ip_auth_priority || 'whitelist');\n                    $('select[name=\"display_level\"]').val(config.display_level || '1');\n                    \n                    // 根据 use_getip 的状态更新 getip_url 输入框的禁用状态\n                    const useGetip = (config.use_getip || '').toLowerCase() === 'true';\n                    $('input[name=\"getip_url\"]').prop('disabled', !useGetip);\n                    \n                    // 更新其他相关UI元素\n                    updateUIState();\n                    \n                    // 更新地址显示\n                    updateAddresses(config);\n                }\n            });\n        }\n\n        function loadProxies() {\n            $.get(appendToken('/api/proxies'), function(data) {\n                const httpProxies = [];\n                const httpsProxies = [];\n                const socks5Proxies = [];\n                const fullProxies = [];\n                \n                data.proxies.forEach(proxy => {\n                    if (proxy.includes('@') || (proxy.includes('://') && !proxy.match(/^(http|https|socks5):\\/\\/[^@]+:\\d+$/))) {\n                        // 带认证信息或其他完整格式的代理放入完整代理列表\n                        fullProxies.push(proxy);\n                    } else if (proxy.startsWith('http://')) {\n                        // HTTP代理\n                        httpProxies.push(proxy.replace('http://', ''));\n                    } else if (proxy.startsWith('https://')) {\n                        // HTTPS代理\n                        httpsProxies.push(proxy.replace('https://', ''));\n                    } else if (proxy.startsWith('socks5://')) {\n                        // SOCKS5代理\n                        socks5Proxies.push(proxy.replace('socks5://', ''));\n                    } else {\n                        // 没有协议前缀的默认作为HTTP代理\n                        httpProxies.push(proxy);\n                    }\n                });\n                \n                // 更新各个文本框的内容\n                document.getElementById('http-proxy-list').value = httpProxies.join('\\n');\n                document.getElementById('https-proxy-list').value = httpsProxies.join('\\n');\n                document.getElementById('socks5-proxy-list').value = socks5Proxies.join('\\n');\n                document.getElementById('full-proxy-list').value = fullProxies.join('\\n');\n            });\n        }\n\n        function loadIpLists() {\n            $.get(appendToken('/api/ip_lists'), function(data) {\n                $('#whitelist').val(data.whitelist.join('\\n'));\n                $('#blacklist').val(data.blacklist.join('\\n'));\n            });\n        }\n\n        function updateLogs(forceRefresh = false) {\n            const level = $('#log-level').val();\n            const searchText = $('#log-search-input').val().toLowerCase();\n            \n            $.get(appendToken(`/api/logs?level=${level}&search=${encodeURIComponent(searchText)}`), function(data) {\n                const container = $('#log-container');\n                container.empty();\n                \n                data.logs.forEach(log => {\n                    let message = log.message;\n                    \n                    if (searchText) {\n                        const regex = new RegExp(`(${searchText})`, 'gi');\n                        message = message.replace(regex, '<span class=\"log-highlight\">$1</span>');\n                    }\n                    \n                    const logEntry = $(`<div class=\"log-entry log-${log.level}\">\n                        <span class=\"log-time\">${log.time}</span> - \n                        <span class=\"log-level\">${log.level}</span> - \n                        <span class=\"log-message\">${message}</span>\n                    </div>`);\n                    \n                    container.append(logEntry);\n                });\n                \n                if (searchText && data.logs.length > 0) {\n                    const firstHighlight = container.find('.log-highlight').first();\n                    if (firstHighlight.length) {\n                        container.scrollTop(firstHighlight.position().top);\n                    }\n                }\n            });\n        }\n\n        function switchProxy() {\n            $.get(appendToken('/api/switch_proxy'), function(data) {\n                if (data.status === 'success') {\n                    updateStatus(); \n                } else {\n                    console.error('Failed to switch proxy:', data.message);\n                }\n            });\n        }\n\n        function saveProxies() {\n            // 获取各个代理列表的内容\n            const httpProxies = document.getElementById('http-proxy-list').value\n                .split('\\n')\n                .filter(line => line.trim())\n                .map(line => `http://${line.trim()}`);\n            \n            const httpsProxies = document.getElementById('https-proxy-list').value\n                .split('\\n')\n                .filter(line => line.trim())\n                .map(line => `https://${line.trim()}`);\n            \n            const socks5Proxies = document.getElementById('socks5-proxy-list').value\n                .split('\\n')\n                .filter(line => line.trim())\n                .map(line => `socks5://${line.trim()}`);\n            \n            const fullProxies = document.getElementById('full-proxy-list').value\n                .split('\\n')\n                .filter(line => line.trim());\n            \n            // 合并所有代理列表\n            const allProxies = [...httpProxies, ...httpsProxies, ...socks5Proxies, ...fullProxies];\n            \n            // 获取测试URL\n            const testUrl = document.getElementById('test-target-url').value.trim();\n            \n            // 首先更新配置\n            $.ajax({\n                url: appendToken('/api/config'),\n                method: 'POST',\n                contentType: 'application/json',\n                data: JSON.stringify({ test_url: testUrl }),\n                success: function(configResponse) {\n                    // 然后保存代理列表\n                    $.ajax({\n                        url: appendToken('/api/proxies'),\n                        method: 'POST',\n                        contentType: 'application/json',\n                        data: JSON.stringify({ proxies: allProxies }),\n                        success: function(response) {\n                            if (response.status === 'success' && configResponse.status === 'success') {\n                                showNotification(translations[currentLanguage].proxy_save_success, 'success');\n                                // 重新加载代理列表以确保显示正确\n                                loadProxies();\n                            } else {\n                                showNotification(translations[currentLanguage].proxy_save_failed.format(response.message), 'error');\n                            }\n                        },\n                        error: function(xhr) {\n                            showNotification(translations[currentLanguage].proxy_save_failed.format(xhr.responseText), 'error');\n                        }\n                    });\n                },\n                error: function(xhr) {\n                    showNotification(translations[currentLanguage].config_save_failed.format(xhr.responseText), 'error');\n                }\n            });\n        }\n\n        function checkProxies() {\n            const testUrl = $('#test-target-url').val().trim() || 'https://www.baidu.com';\n            \n            // 显示加载状态\n            const saveBtn = document.querySelector('.proxy-actions .btn-primary');\n            const checkBtn = document.querySelector('.proxy-actions .btn-warning');\n            saveBtn.disabled = true;\n            checkBtn.disabled = true;\n            checkBtn.innerHTML = `<i class=\"fa fa-spinner fa-spin\"></i> ${translations[currentLanguage].checking_proxy}`;\n            \n            $.get(appendToken('/api/check_proxies'), { test_url: testUrl }, function(data) {\n                if (data.status === 'success') {\n                    loadProxies();\n                    showNotification(translations[currentLanguage].proxy_check_result.replace('{}', data.total), 'success');\n                } else {\n                    showNotification(translations[currentLanguage].proxy_check_failed.format(data.message), 'error');\n                }\n            }).always(function() {\n                // 恢复按钮状态\n                saveBtn.disabled = false;\n                checkBtn.disabled = false;\n                checkBtn.innerHTML = `<i class=\"fa fa-check-circle\"></i> <span>${translations[currentLanguage].check_proxy_btn}</span>`;\n            });\n        }\n\n        function saveIpLists() {\n            const whitelist = $('#whitelist').val().split('\\n').filter(line => line.trim());\n            const blacklist = $('#blacklist').val().split('\\n').filter(line => line.trim());\n            const ipAuthPriority = $('select[name=\"ip_auth_priority\"]').val();\n            \n            // 显示加载状态\n            const saveBtn = document.querySelector('#save-ip-list-btn');\n            if (saveBtn) {\n                const originalContent = saveBtn.innerHTML;\n                saveBtn.innerHTML = `<i class=\"fa fa-spinner fa-spin\"></i> ${translations[currentLanguage].loading}`;\n                saveBtn.disabled = true;\n            }\n\n            // 先保存白名单\n            $.ajax({\n                url: appendToken('/api/ip_lists'),\n                method: 'POST',\n                contentType: 'application/json',\n                data: JSON.stringify({ \n                    type: 'whitelist', \n                    list: whitelist \n                })\n            })\n            .then(function(whitelistResponse) {\n                // 再保存黑名单\n                return $.ajax({\n                    url: appendToken('/api/ip_lists'),\n                    method: 'POST',\n                    contentType: 'application/json',\n                    data: JSON.stringify({ \n                        type: 'blacklist', \n                        list: blacklist \n                    })\n                });\n            })\n            .then(function(response) {\n                if (response.status === 'success') {\n                    showNotification(translations[currentLanguage].ip_list_save_success, 'success');\n                    // 重新加载IP名单以确保显示正确\n                    loadIpLists();\n                } else {\n                    showNotification(translations[currentLanguage].ip_list_save_failed.format(response.message), 'error');\n                }\n            })\n            .catch(function(xhr) {\n                showNotification(translations[currentLanguage].ip_list_save_failed.format(xhr.responseText), 'error');\n            })\n            .always(function() {\n                // 恢复按钮状态\n                const saveBtn = document.querySelector('#save-ip-list-btn');\n                if (saveBtn) {\n                    saveBtn.innerHTML = `<i class=\"fa fa-save\"></i> <span data-i18n=\"save_list_btn\">${translations[currentLanguage].save_list_btn}</span>`;\n                    saveBtn.disabled = false;\n                }\n            });\n        }\n\n        function controlService(action) {\n            const btn = document.querySelector(`.${action}-btn`);\n            if (btn) {\n                btn.classList.add('loading');\n            }\n\n            $.ajax({\n                url: appendToken('/api/service'),\n                method: 'POST',\n                contentType: 'application/json',\n                data: JSON.stringify({ action: action }),\n                success: function(response) {\n                    if (response.status === 'success') {\n                        // 立即更新状态显示\n                        const statusText = document.querySelector('.status-text');\n                        const statusDot = document.querySelector('.status-dot');\n                        \n                        if (statusText) {\n                            statusText.textContent = translations[currentLanguage].service_status[response.service_status];\n                        }\n                        \n                        if (statusDot) {\n                            statusDot.classList.remove('running');\n                            statusDot.classList.remove('stopped');\n                            void statusDot.offsetWidth; // 触发重排以重置动画\n                            statusDot.classList.add(response.service_status === 'running' ? 'running' : 'stopped');\n                        }\n                        \n                        // 更新按钮状态\n                        updateServiceButtons(response.service_status);\n                        \n                        // 更新状态后立即触发一次状态更新\n                        updateStatus();\n                    }\n                    showToast(response.message);\n                },\n                error: function(xhr) {\n                    showToast(translations[currentLanguage].operation_failed.replace('{}', xhr.responseText));\n                },\n                complete: function() {\n                    if (btn) {\n                        btn.classList.remove('loading');\n                        // 移除所有可能的过渡状态类\n                        btn.classList.remove('stopping', 'starting', 'restarting');\n                    }\n                }\n            });\n        }\n\n        function clearLogs() {\n            if (confirm(translations[currentLanguage].confirm_clear_logs)) {\n                $.ajax({\n                    url: appendToken('/api/logs/clear'),\n                    method: 'POST',\n                    success: function(response) {\n                        if (response.status === 'success') {\n                            showNotification(translations[currentLanguage].logs_cleared, 'success');\n                            $('#log-container').empty();\n                        } else {\n                            showNotification(translations[currentLanguage].clear_logs_failed.format(response.message), 'error');\n                        }\n                    },\n                    error: function(xhr) {\n                        showNotification(translations[currentLanguage].clear_logs_failed.format(xhr.responseText), 'error');\n                    }\n                });\n            }\n        }\n\n        // 添加保存所有配置的函数\n        function saveAllConfig() {\n            // 显示加载状态\n            const saveBtn = document.querySelector('.floating-save-btn');\n            const originalContent = saveBtn.innerHTML;\n            saveBtn.innerHTML = `<i class=\"fa fa-spinner fa-spin\"></i> <span data-i18n=\"saving\">保存中...</span>`;\n            saveBtn.disabled = true;\n\n            // 收集所有配置\n            const config = {\n                port: $('input[name=\"port\"]').val(),\n                mode: $('select[name=\"mode\"]').val(),\n                interval: $('input[name=\"interval\"]').val(),\n                username: $('input[name=\"username\"]').val(),\n                password: $('input[name=\"password\"]').val(),\n                // 修改这里，确保正确获取 checkbox 的值并转换为字符串\n                use_getip: $('input[name=\"use_getip\"]').prop('checked').toString(),\n                getip_url: $('input[name=\"getip_url\"]').val(),\n                proxy_username: $('input[name=\"proxy_username\"]').val(),\n                proxy_password: $('input[name=\"proxy_password\"]').val(),\n                proxy_file: $('input[name=\"proxy_file\"]').val(),\n                check_proxies: $('input[name=\"check_proxies\"]').prop('checked').toString(),\n                language: $('select[name=\"language\"]').val(),\n                whitelist_file: $('input[name=\"whitelist_file\"]').val(),\n                blacklist_file: $('input[name=\"blacklist_file\"]').val(),\n                ip_auth_priority: $('select[name=\"ip_auth_priority\"]').val(),\n                display_level: $('select[name=\"display_level\"]').val(),\n                test_url: $('input[name=\"test_url\"]').val()\n            };\n\n            // 保存配置\n            $.ajax({\n                url: appendToken('/api/config'),\n                method: 'POST',\n                contentType: 'application/json',\n                data: JSON.stringify(config),\n                success: function(response) {\n                    if (response.status === 'success') {\n                        showNotification(translations[currentLanguage].config_save_success, 'success');\n                        \n                        if (response.port_changed) {\n                            showNotification(translations[currentLanguage].restarting_service, 'success');\n                            // 调用服务重启API\n                            $.ajax({\n                                url: appendToken('/api/service'),\n                                method: 'POST',\n                                contentType: 'application/json',\n                                data: JSON.stringify({ action: 'restart' }),\n                                success: function(restartResponse) {\n                                    if (restartResponse.status === 'success') {\n                                        showNotification(translations[currentLanguage].service_restart_success, 'success');\n                                    } else {\n                                        showNotification(translations[currentLanguage].service_restart_failed, 'error');\n                                    }\n                                    updateStatus();\n                                },\n                                error: function(xhr) {\n                                    showNotification(translations[currentLanguage].service_restart_failed.format(xhr.responseText), 'error');\n                                }\n                            });\n                        }\n                        \n                        // 重新加载配置以更新UI状态\n                        loadConfig();\n                    } else {\n                        showNotification(translations[currentLanguage].config_save_failed.format(response.message), 'error');\n                    }\n                },\n                error: function(xhr) {\n                    showNotification(translations[currentLanguage].config_save_failed.format(xhr.responseText), 'error');\n                },\n                complete: function() {\n                    // 恢复按钮状态\n                    saveBtn.innerHTML = originalContent;\n                    saveBtn.disabled = false;\n                }\n            });\n        }\n\n        // 添加一个简单的 Toast 提示函数\n        function showToast(type, message) {\n            const toast = document.createElement('div');\n            toast.className = `toast toast-${type}`;\n            toast.innerHTML = `\n                <i class=\"fa fa-${type === 'success' ? 'check' : 'exclamation'}-circle\"></i>\n                <span>${message}</span>\n            `;\n            document.body.appendChild(toast);\n            \n            // 添加显示类触发动画\n            setTimeout(() => toast.classList.add('show'), 100);\n            \n            // 3秒后移除\n            setTimeout(() => {\n                toast.classList.remove('show');\n                setTimeout(() => toast.remove(), 300);\n            }, 3000);\n        }\n\n        const translations = {\n            cn: {\n                'title': 'ProxyCat 控制面板',\n                'page_title': 'ProxyCat 控制面板',\n                'current_proxy_title': '当前代理',\n                'run_mode_title': '运行模式',\n                'proxy_total_title': '代理总数',\n                'next_switch_title': '下次切换',\n                'service_control_title': '服务控制',\n                'proxy_settings_title': '代理设置',\n                'local_addresses_title': '本地监听地址',\n                'about_info_title': '关于信息',\n                'star_project_text': '开源项目求 Star: ',\n                'public_account_text': '公众号: ',\n                'public_account_name': '樱花庄的本间白猫',\n                'github_link': 'https://github.com/honmashironeko/ProxyCat',\n                'local_http_text': '本地监听地址 (HTTP): ',\n                'local_socks5_text': '本地监听地址 (SOCKS5): ',\n                'current_proxy': '当前代理',\n                'run_mode': '运行模式',\n                'proxy_count': '代理总数',\n                'service_control': '服务控制',\n                'start_service': '启动服务',\n                'stop_service': '停止服务',\n                'restart_service': '重启服务',\n                'config_settings': '配置设置',\n                'proxy_management': '代理管理',\n                'ip_lists': 'IP名单',\n                'run_logs': '运行日志',\n                'basic_config': '基本配置',\n                'port': '端口',\n                'switch_interval': '切换间隔(秒)',\n                'run_mode_text': '运行模式',\n                'cycle_mode': '循环模式',\n                'loadbalance_mode': '负载均衡',\n                'language_text': '语言',\n                'auth_config': '认证配置',\n                'username': '用户名',\n                'password': '密码',\n                'proxy_settings': '代理设置',\n                'use_api': '使用API获取代理',\n                'api_address': 'API地址',\n                'enable_proxy_check': '启用代理检测',\n                'proxy_list': '代理列表',\n                'save_proxy': '保存代理',\n                'check_proxy': '检测代理',\n                'whitelist': '白名单',\n                'blacklist': '黑名单',\n                'ip_auth_priority': 'IP认证优先级',\n                'whitelist_first': '白名单优先',\n                'blacklist_first': '黑名单优先',\n                'save_list': '保存名单',\n                'log_level': '日志级别',\n                'all': '全部',\n                'info': '信息',\n                'warning': '警告',\n                'critical': '严重错误',\n                'save_config': '保存配置',\n                'proxy_username': '代理用户名',\n                'proxy_password': '代理密码',\n                'clear_logs': '清除日志',\n                'confirm_clear_logs': '确定要清除所有日志吗？此操作不可恢复。',\n                'logs_cleared': '日志已清除',\n                'clear_logs_failed': '清除日志失败：{}',\n                'config_tab': '配置设置',\n                'proxies_tab': '代理管理',\n                'ip_lists_tab': 'IP名单',\n                'logs_tab': '运行日志',\n                'port_label': '端口',\n                'save_success': '保存成功',\n                'save_failed': '保存失败',\n                'confirm_restart': '配置已更改，需要重启服务器才能生效。是否立即重启？',\n                'service_start_success': '服务启动成功',\n                'service_stop_success': '服务停止成功',\n                'service_restart_success': '服务重启成功',\n                'operation_failed': '操作失败：{}',\n                'loading': '加载中...',\n                'run_mode_title': '运行模式',\n                'proxy_total': '代理总数',\n                'next_switch_title': '下次切换',\n                'basic_config_title': '基本配置',\n                'switch_interval_label': '切换间隔(秒)',\n                'run_mode_label': '运行模式',\n                'cycle_mode': '循环模式',\n                'loadbalance_mode': '负载均衡',\n                'auth_config_title': '认证配置',\n                'use_api_label': '使用API获取代理',\n                'api_url_label': 'API地址',\n                'enable_proxy_check_label': '代理有效性检测',\n                'proxy_list_label': '代理列表',\n                'save_proxy_btn': '保存代理',\n                'check_proxy_btn': '检测代理',\n                'whitelist_title': '白名单',\n                'blacklist_title': '黑名单',\n                'ip_auth_priority_label': 'IP认证优先级',\n                'whitelist_first': '白名单优先',\n                'blacklist_first': '黑名单优先',\n                'save_list_btn': '保存名单',\n                'proxy_check_success': '代理检测完成，有效代理数：{}',\n                'proxy_check_failed': '代理检测失败：{}',\n                'proxy_save_success': '代理保存成功',\n                'proxy_save_failed': '代理保存失败：{}',\n                'ip_list_save_success': 'IP名单保存成功',\n                'ip_list_save_failed': 'IP名单保存失败：{}',\n                'manual_switch_btn': '手动切换',\n                'switch_success': '切换成功',\n                'switch_failed': '切换失败：{}',\n                'no_proxy': '没有代理',\n                'seconds': '秒',\n                'username_label': '用户名',\n                'password_label': '密码',\n                'proxy_username_label': '代理用户名',\n                'proxy_password_label': '代理密码',\n                'progress_bar_label': '切换进度',\n                'auth_username_label': '认证用户名',\n                'auth_password_label': '认证密码',\n                'proxy_auth_username_label': '代理认证用户名',\n                'proxy_auth_password_label': '代理认证密码',\n                'language_switch_success': '语言切换成功',\n                'language_switch_failed': '语言切换失败：{}',\n                'refresh_failed': '刷新数据失败：{}',\n                'log_level_label': '日志级别',\n                'log_level_all': '全部',\n                'log_level_info': '信息',\n                'log_level_warning': '警告',\n                'log_level_error': '错误',\n                'log_level_critical': '严重错误',\n                'clear_logs_btn': '清除日志',\n                'confirm_clear_logs': '确定要清除所有日志吗？此操作不可恢复。',\n                'logs_cleared': '日志已清除',\n                'clear_logs_failed': '清除日志失败：{}',\n                'logs_tab': '运行日志',\n                'config_save_success': '配置保存成功',\n                'config_save_failed': '配置保存失败：{}',\n                'proxy_save_success': '代理保存成功',\n                'proxy_save_failed': '代理保存失败：{}',\n                'ip_list_save_success': 'IP名单保存成功',\n                'ip_list_save_failed': 'IP名单保存失败：{}',\n                'service_start_success': '服务启动成功',\n                'service_stop_success': '服务停止成功',\n                'service_restart_success': '服务重启成功',\n                'confirm_restart': '配置已更改，需要重启服务器才能生效。是否立即重启？',\n                'invalid_token': '无效的访问令牌，请检查 URL 中的 token 参数',\n                'version_info_not_found': '未找到版本信息',\n                'update_check_error': '检查更新失败：{}',\n                'version_check_failed': '版本检查失败：{}',\n                'proxy_check_start': '开始检查代理...',\n                'proxy_check_complete': '代理检查完成',\n                'proxy_check_result': '代理检查完成，有效代理：{}个',\n                'proxy_check_failed': '代理检查失败：{}',\n                'language_label': '语言',\n                'test_url_label': '代理检测地址',\n                'test_url_help': '用于检测代理有效性的目标地址',\n                'invalid_test_url': '无效的检测地址',\n                'user_management_title': '用户管理',\n                'username_column': '用户名',\n                'password_column': '密码',\n                'actions_column': '操作',\n                'add_user_btn': '添加用户',\n                'enter_username': '请输入用户名',\n                'enter_password': '请输入密码',\n                'confirm_delete_user': '确定要删除该用户吗？',\n                'users_save_success': '用户保存成功',\n                'users_save_failed': '用户保存失败：{}',\n                'enter_new_password': '请输入新密码',\n                'password_changed': '密码修改成功',\n                'password_change_failed': '密码修改失败：{}',\n                'api_proxy_settings_title': 'API代理设置',\n                'test_target_label': '测试目标地址',\n                'test_url_help': '用于检测代理有效性的目标地址',\n                'test_target_placeholder': 'https://www.baidu.com',\n                'service_control_description': '管理代理服务器的运行状态',\n                'service_running': '运行中',\n                'service_stopped': '已停止',\n                'service_running_desc': '系统正常运行，代理切换功能已启用',\n                'service_stopped_desc': '系统已停止，代理切换功能未启用',\n                'service_status': {\n                    'running': '运行中',\n                    'stopped': '已停止'\n                },\n                'no_proxy': '无代理',\n                'seconds': '秒',\n                'service_control_title': '服务控制',\n                'start_service': '启动',\n                'stop_service': '停止',\n                'restart_service': '重启',\n                'loading': '加载中...',\n                'manual_switch_btn': '手动切换',\n                'cycle_mode': '循环模式',\n                'loadbalance_mode': '负载均衡',\n                'next_switch': '下次切换',\n                'proxy_mode': {\n                    'cycle': '循环模式',\n                    'loadbalance': '负载均衡'\n                },\n                'service_action': {\n                    'start_success': '服务启动成功',\n                    'stop_success': '服务停止成功',\n                    'restart_success': '服务重启成功'\n                },\n                'operation_failed': '操作失败: {}',\n                'no_proxy': '无代理',\n                'proxy_management': '代理管理',\n                'http_proxy': 'HTTP代理',\n                'https_proxy': 'HTTPS代理',\n                'socks5_proxy': 'SOCKS5代理',\n                'full_proxy': '完整代理地址',\n                'proxy_help': '支持完整格式的代理地址，包含协议和认证信息',\n                'proxy_placeholder': {\n                    'http': '每行一个地址，格式：ip:port\\n例如：127.0.0.1:8080',\n                    'https': '每行一个地址，格式：ip:port\\n例如：127.0.0.1:8443',\n                    'socks5': '每行一个地址，格式：ip:port\\n例如：127.0.0.1:1080',\n                    'full': '每行一个地址，格式：protocol://[username:password@]ip:port\\n例如：http://user:pass@127.0.0.1:8080'\n                },\n                'save_proxy_btn': '保存代理',\n                'check_proxy_btn': '检测代理',\n                'checking_proxy': '检测中...',\n                'proxy_check_success': '代理检测完成，有效代理数：{}',\n                'proxy_check_failed': '代理检测失败：{}',\n                'version_text': '当前版本:',\n                'latest_version': '已是最新版本',\n                'new_version_available': '发现新版本',\n                'version_check_failed': '版本检查失败',\n                'restarting_service': '正在重启服务...',\n                'service_restart_success': '服务重启成功',\n                'service_restart_failed': '服务重启失败',\n                'search_logs': '搜索日志...'\n            },\n            en: {\n                'title': 'ProxyCat Control Panel',\n                'page_title': 'ProxyCat Control Panel',\n                'current_proxy_title': 'Current Proxy',\n                'run_mode_title': 'Run Mode',\n                'proxy_total_title': 'Total Proxies',\n                'next_switch_title': 'Next Switch',\n                'service_control_title': 'Service Control',\n                'proxy_settings_title': 'Proxy Settings',\n                'local_addresses_title': 'Local Listening Addresses',\n                'about_info_title': 'About',\n                'star_project_text': 'Star Project on GitHub: ',\n                'public_account_text': 'WeChat Official Account: ',\n                'public_account_name': 'Honma Shirone Cat',\n                'github_link': 'https://github.com/honmashironeko/ProxyCat',\n                'local_http_text': 'Local HTTP Address: ',\n                'local_socks5_text': 'Local SOCKS5 Address: ',\n                'current_proxy': 'Current Proxy',\n                'run_mode': 'Run Mode',\n                'proxy_count': 'Proxy Count',\n                'service_control': 'Service Control',\n                'start_service': 'Start Service',\n                'stop_service': 'Stop Service',\n                'restart_service': 'Restart Service',\n                'config_settings': 'Config Settings',\n                'proxy_management': 'Proxy Management',\n                'ip_lists': 'IP Lists',\n                'run_logs': 'Run Logs',\n                'basic_config': 'Basic Config',\n                'port': 'Port',\n                'switch_interval': 'Switch Interval(s)',\n                'run_mode_text': 'Run Mode',\n                'cycle_mode': 'Cycle Mode',\n                'loadbalance_mode': 'Load Balance',\n                'language_text': 'Language',\n                'auth_config': 'Auth Config',\n                'username': 'Username',\n                'password': 'Password',\n                'proxy_settings': 'Proxy Settings',\n                'use_api': 'Use API',\n                'api_address': 'API Address',\n                'enable_proxy_check': 'Enable Proxy Check',\n                'proxy_list': 'Proxy List',\n                'save_proxy': 'Save Proxy',\n                'check_proxy': 'Check Proxy',\n                'whitelist': 'Whitelist',\n                'blacklist': 'Blacklist',\n                'ip_auth_priority': 'IP Auth Priority',\n                'whitelist_first': 'Whitelist First',\n                'blacklist_first': 'Blacklist First',\n                'save_list': 'Save List',\n                'log_level': 'Log Level',\n                'all': 'All',\n                'info': 'Info',\n                'warning': 'Warning',\n                'error': 'Error',\n                'critical': 'Critical',\n                'save_config': 'Save Config',\n                'proxy_username': 'Proxy Username',\n                'proxy_password': 'Proxy Password',\n                'clear_logs': 'Clear Logs',\n                'confirm_clear_logs': 'Are you sure you want to clear all logs? This action cannot be undone.',\n                'logs_cleared': 'Logs cleared',\n                'clear_logs_failed': 'Failed to clear logs: {}',\n                'config_tab': 'Configuration',\n                'proxies_tab': 'Proxy Management',\n                'ip_lists_tab': 'IP Lists',\n                'logs_tab': 'Logs',\n                'port_label': 'Port',\n                'save_success': 'Save successful',\n                'save_failed': 'Save failed',\n                'confirm_restart': 'Configuration has changed, server restart is required. Restart now?',\n                'service_start_success': 'Service started successfully',\n                'service_stop_success': 'Service stopped successfully',\n                'service_restart_success': 'Service restarted successfully',\n                'operation_failed': 'Operation failed: {}',\n                'loading': 'Loading...',\n                'run_mode_title': 'Run Mode',\n                'proxy_total': 'Total Proxies',\n                'next_switch_title': 'Next Switch',\n                'basic_config_title': 'Basic Configuration',\n                'switch_interval_label': 'Switch Interval(s)',\n                'run_mode_label': 'Run Mode',\n                'cycle_mode': 'Cycle Mode',\n                'loadbalance_mode': 'Load Balance',\n                'auth_config_title': 'Authentication',\n                'use_api_label': 'Use API for Proxy',\n                'api_url_label': 'API URL',\n                'enable_proxy_check_label': 'Proxy Validity Check',\n                'proxy_list_label': 'Proxy List',\n                'save_proxy_btn': 'Save Proxies',\n                'check_proxy_btn': 'Check Proxies',\n                'whitelist_title': 'Whitelist',\n                'blacklist_title': 'Blacklist',\n                'ip_auth_priority_label': 'IP Auth Priority',\n                'whitelist_first': 'Whitelist First',\n                'blacklist_first': 'Blacklist First',\n                'save_list_btn': 'Save Lists',\n                'proxy_check_success': 'Proxy check completed, valid proxies: {}',\n                'proxy_check_failed': 'Proxy check failed: {}',\n                'proxy_save_success': 'Proxies saved successfully',\n                'proxy_save_failed': 'Save failed: {}',\n                'ip_list_save_success': 'IP lists saved successfully',\n                'ip_list_save_failed': 'Save failed: {}',\n                'manual_switch_btn': 'Manual Switch',\n                'switch_success': 'Switch successful',\n                'switch_failed': 'Switch failed: {}',\n                'no_proxy': 'No Proxy',\n                'seconds': 's',\n                'username_label': 'Username',\n                'password_label': 'Password',\n                'proxy_username_label': 'Proxy Username',\n                'proxy_password_label': 'Proxy Password',\n                'progress_bar_label': 'Switch Progress',\n                'auth_username_label': 'Auth Username',\n                'auth_password_label': 'Auth Password',\n                'proxy_auth_username_label': 'Proxy Auth Username',\n                'proxy_auth_password_label': 'Proxy Auth Password',\n                'language_switch_success': 'Language switched successfully',\n                'language_switch_failed': 'Failed to switch language: {}',\n                'refresh_failed': 'Failed to refresh data: {}',\n                'log_level_label': 'Log Level',\n                'log_level_all': 'All',\n                'log_level_info': 'Info',\n                'log_level_warning': 'Warning',\n                'log_level_error': 'Error',\n                'log_level_critical': 'Critical',\n                'clear_logs_btn': 'Clear Logs',\n                'confirm_clear_logs': 'Are you sure you want to clear all logs? This action cannot be undone.',\n                'logs_cleared': 'Logs cleared',\n                'clear_logs_failed': 'Failed to clear logs: {}',\n                'logs_tab': 'Logs',\n                'config_save_success': 'Configuration saved successfully',\n                'config_save_failed': 'Failed to save configuration: {}',\n                'proxy_save_success': 'Proxies saved successfully',\n                'proxy_save_failed': 'Failed to save proxies: {}',\n                'ip_list_save_success': 'IP lists saved successfully',\n                'ip_list_save_failed': 'Failed to save IP lists: {}',\n                'service_start_success': 'Service started successfully',\n                'service_stop_success': 'Service stopped successfully',\n                'service_restart_success': 'Service restarted successfully',\n                'confirm_restart': 'Configuration has changed, server restart is required. Restart now?',\n                'invalid_token': 'Invalid access token, please check the token parameter in URL',\n                'version_info_not_found': 'Version information not found',\n                'update_check_error': 'Failed to check for updates: {}',\n                'version_check_failed': 'Version check failed: {}',\n                'proxy_check_start': 'Starting proxy check...',\n                'proxy_check_complete': 'Proxy check completed',\n                'proxy_check_result': 'Proxy check completed, valid proxies: {}',\n                'proxy_check_failed': 'Proxy check failed: {}',\n                'language_label': 'Language',\n                'test_url_label': 'Proxy Test URL',\n                'test_url_help': 'Target URL for proxy validity check',\n                'invalid_test_url': 'Invalid test URL',\n                'user_management_title': 'User Management',\n                'username_column': 'Username',\n                'password_column': 'Password',\n                'actions_column': 'Actions',\n                'add_user_btn': 'Add User',\n                'enter_username': 'Enter username',\n                'enter_password': 'Enter password',\n                'confirm_delete_user': 'Are you sure you want to delete this user?',\n                'users_save_success': 'Users saved successfully',\n                'users_save_failed': 'Failed to save users: {}',\n                'enter_new_password': 'Enter new password',\n                'password_changed': 'Password changed successfully',\n                'password_change_failed': 'Failed to change password: {}',\n                'api_proxy_settings_title': 'API Proxy Settings',\n                'test_target_label': 'Test Target URL',\n                'test_url_help': 'Target URL for proxy validity check',\n                'test_target_placeholder': 'https://www.baidu.com',\n                'service_control_description': 'Manage proxy server running status',\n                'service_status': {\n                    'running': 'Running',\n                    'stopped': 'Stopped'\n                },\n                'no_proxy': 'No Proxy',\n                'seconds': 's',\n                'service_control_title': 'Service Control',\n                'start_service': 'Start',\n                'stop_service': 'Stop',\n                'restart_service': 'Restart',\n                'proxy_management': 'Proxy Management',\n                'http_proxy': 'HTTP Proxy',\n                'https_proxy': 'HTTPS Proxy',\n                'socks5_proxy': 'SOCKS5 Proxy',\n                'full_proxy': 'Full Proxy Address',\n                'proxy_help': 'Support full format proxy addresses, including protocol and authentication',\n                'proxy_placeholder': {\n                    'http': 'One address per line, format: ip:port\\nExample: 127.0.0.1:8080',\n                    'https': 'One address per line, format: ip:port\\nExample: 127.0.0.1:8443',\n                    'socks5': 'One address per line, format: ip:port\\nExample: 127.0.0.1:1080',\n                    'full': 'One address per line, format: protocol://[username:password@]ip:port\\nExample: http://user:pass@127.0.0.1:8080'\n                },\n                'save_proxy_btn': 'Save Proxies',\n                'check_proxy_btn': 'Check Proxies',\n                'checking_proxy': 'Checking...',\n                'proxy_check_success': 'Proxy check completed, valid proxies: {}',\n                'proxy_check_failed': 'Proxy check failed: {}',\n                'version_text': 'Current Version:',\n                'latest_version': 'Up to date',\n                'new_version_available': 'New version available',\n                'version_check_failed': 'Version check failed',\n                'restarting_service': 'Restarting service...',\n                'service_restart_success': 'Service restarted successfully',\n                'service_restart_failed': 'Failed to restart service',\n                'search_logs': 'Search logs...'\n            }\n        };\n\n        let currentLanguage = 'cn';\n\n        function updatePageLanguage(language) {\n            const texts = translations[language];\n            \n            // 更新所有带有 data-i18n 属性的元素\n            for (const [key, value] of Object.entries(texts)) {\n                const elements = document.querySelectorAll(`[data-i18n=\"${key}\"]`);\n                elements.forEach(el => el.textContent = value);\n                \n                // 更新带有 data-i18n-placeholder 属性的元素\n                const placeholderElements = document.querySelectorAll(`[data-i18n-placeholder=\"${key}\"]`);\n                placeholderElements.forEach(el => el.setAttribute('placeholder', value));\n            }\n            \n            // 更新语言切换按钮显示\n            document.querySelector('.cn-text').style.display = language === 'cn' ? 'inline' : 'none';\n            document.querySelector('.en-text').style.display = language === 'en' ? 'inline' : 'none';\n            \n            // 更新日志级别选项\n            updateLogLevelOptions();\n\n            // 更新代理列表标题\n            const proxyHeaders = document.querySelectorAll('.proxy-list-header span');\n            proxyHeaders.forEach(header => {\n                const headerText = header.textContent.toLowerCase();\n                if (headerText.includes('http') && !headerText.includes('https')) {\n                    header.textContent = translations[language].http_proxy;\n                } else if (headerText.includes('https')) {\n                    header.textContent = translations[language].https_proxy;\n                } else if (headerText.includes('socks5')) {\n                    header.textContent = translations[language].socks5_proxy;\n                }\n            });\n\n            // 更新代理列表占位符\n            document.getElementById('http-proxy-list').setAttribute('placeholder', translations[language].proxy_placeholder.http);\n            document.getElementById('https-proxy-list').setAttribute('placeholder', translations[language].proxy_placeholder.https);\n            document.getElementById('socks5-proxy-list').setAttribute('placeholder', translations[language].proxy_placeholder.socks5);\n            \n            // 更新帮助文本\n            const helpText = document.querySelector('.proxy-list-help');\n            if (helpText) {\n                helpText.textContent = translations[language].proxy_help;\n            }\n\n            // 更新占位符文本\n            const placeholders = {\n                'http-proxy-list': translations[language].proxy_placeholder.http,\n                'https-proxy-list': translations[language].proxy_placeholder.https,\n                'socks5-proxy-list': translations[language].proxy_placeholder.socks5,\n                'full-proxy-list': translations[language].proxy_placeholder.full\n            };\n\n            Object.entries(placeholders).forEach(([id, text]) => {\n                const element = document.getElementById(id);\n                if (element) {\n                    element.setAttribute('placeholder', text);\n                }\n            });\n\n            // 更新按钮文本\n            const saveBtn = document.querySelector('.proxy-actions .btn-primary span');\n            const checkBtn = document.querySelector('.proxy-actions .btn-warning span');\n            if (saveBtn) {\n                saveBtn.textContent = translations[language].save_proxy_btn;\n            }\n            if (checkBtn) {\n                checkBtn.textContent = translations[language].check_proxy_btn;\n            }\n        }\n\n        // 修改初始化语言函数\n        async function initializeLanguage() {\n            try {\n                const response = await fetch(appendToken('/api/status'));\n                const data = await response.json();\n                currentLanguage = data.config.language || 'cn';\n                \n                // 更新所有翻译文本\n                updatePageLanguage(currentLanguage);\n                \n                // 特别更新服务状态文本\n                const status = data.service_status || 'running';\n                const statusText = document.querySelector('.status-text');\n                if (statusText) {\n                    statusText.textContent = translations[currentLanguage].service_status[status];\n                }\n                \n                // 更新按钮状态\n                updateServiceButtons(status);\n                \n                // 确保在DOM加载完成后更新\n                if (document.readyState === 'loading') {\n                    document.addEventListener('DOMContentLoaded', () => {\n                        updatePageLanguage(currentLanguage);\n                        const statusText = document.querySelector('.status-text');\n                        if (statusText) {\n                            statusText.textContent = translations[currentLanguage].service_status[status];\n                        }\n                    });\n                }\n            } catch (error) {\n                console.error('Failed to initialize language:', error);\n            }\n        }\n\n        // 修改语言切换处理函数\n        document.getElementById('languageToggle').addEventListener('click', async function() {\n            const newLanguage = currentLanguage === 'cn' ? 'en' : 'cn';\n            \n            try {\n                const response = await fetch(appendToken('/api/language'), {\n                    method: 'POST',\n                    headers: {\n                        'Content-Type': 'application/json'\n                    },\n                    body: JSON.stringify({ language: newLanguage })\n                });\n                \n                const data = await response.json();\n                if (data.status === 'success') {\n                    currentLanguage = newLanguage;\n                    \n                    // 更新所有翻译文本\n                    updatePageLanguage(newLanguage);\n                    \n                    // 特别更新服务状态文本\n                    const statusResponse = await fetch(appendToken('/api/status'));\n                    const statusData = await statusResponse.json();\n                    const status = statusData.service_status || 'running';\n                    const statusText = document.querySelector('.status-text');\n                    if (statusText) {\n                        statusText.textContent = translations[newLanguage].service_status[status];\n                    }\n                } else {\n                    console.error('Failed to switch language:', data.message);\n                }\n            } catch (error) {\n                console.error('Failed to switch language:', error);\n            }\n        });\n\n        document.addEventListener('DOMContentLoaded', initializeLanguage);\n\n        loadConfig();\n        loadProxies();\n        loadIpLists();\n        \n        setInterval(updateStatus, 2000); \n        setInterval(updateCountdown, 1000); \n        setInterval(updateLogs, 2000);\n        updateStatus();\n        updateCountdown();\n        updateLogs();\n        function showMessage(key, ...args) {\n            const message = translations[currentLanguage][key];\n            if (args.length > 0) {\n                try {\n                    showNotification(message.replace('{}', args.join(', ')), key.includes('success') ? 'success' : 'error');\n                } catch (e) {\n                    showNotification(message, key.includes('success') ? 'success' : 'error');\n                }\n            } else {\n                showNotification(message, key.includes('success') ? 'success' : 'error');\n            }\n        }\n\n        function updateLogLevelOptions() {\n            const logLevelSelect = document.getElementById('log-level');\n            const options = logLevelSelect.options;\n            for (let i = 0; i < options.length; i++) {\n                const option = options[i];\n                const key = `log_level_${option.value.toLowerCase()}`;\n                if (translations[currentLanguage][key]) {\n                    option.text = translations[currentLanguage][key];\n                }\n            }\n        }\n\n        async function checkVersion() {\n            try {\n                const lastCheck = localStorage.getItem('lastVersionCheck');\n                const now = Date.now();\n                \n                if (!lastCheck || (now - parseInt(lastCheck)) > 24 * 60 * 60 * 1000) {\n                    const response = await fetch(appendToken('/api/version'));\n                    const data = await response.json();\n                    \n                    if (data.status === 'success' && !data.is_latest) {\n                        const message = translations[currentLanguage].version_update_available\n                            .replace('{}', data.current_version)\n                            .replace('{}', data.latest_version);\n                        \n                        alert(message);\n                    }\n                    \n                    localStorage.setItem('lastVersionCheck', now.toString());\n                }\n            } catch (error) {\n                console.error('Failed to check version:', error);\n            }\n        }\n\n        document.addEventListener('DOMContentLoaded', function() {\n            setTimeout(checkVersion, 3000); \n        });\n\n        $(document).ready(function() {\n            $('#test-target-url').attr('placeholder', \n                translations[currentLanguage]['test_target_placeholder']);\n        });\n\n        async function switchLanguage(newLanguage) {\n            try {\n                const response = await fetch(appendToken('/api/language'), {\n                    method: 'POST',\n                    headers: { 'Content-Type': 'application/json' },\n                    body: JSON.stringify({ language: newLanguage })\n                });\n                \n                const data = await response.json();\n                if (data.status === 'success') {\n                    currentLanguage = newLanguage;\n                    updatePageLanguage(newLanguage);\n                    await Promise.all([refreshStatus(), refreshLogs()]);\n                }\n            } catch (error) {\n                console.error('Failed to switch language:', error);\n            }\n        }\n\n        function loadUsers() {\n            $.get(appendToken('/api/users'), function(data) {\n                const tbody = $('#user-table tbody');\n                tbody.empty();\n                \n                Object.entries(data.users).forEach(([username, password]) => {\n                    tbody.append(`\n                        <tr>\n                            <td>${username}</td>\n                            <td>${password}</td>\n                            <td>\n                                <button class=\"btn btn-sm btn-warning\" onclick=\"editUserPassword('${username}')\">\n                                    <i class=\"fa fa-edit\"></i>\n                                </button>\n                                <button class=\"btn btn-sm btn-danger\" onclick=\"deleteUser('${username}')\">\n                                    <i class=\"fa fa-trash\"></i>\n                                </button>\n                            </td>\n                        </tr>\n                    `);\n                });\n                \n                // 更新本地监听地址\n                updateLocalAddresses();\n            });\n        }\n\n        function addUser() {\n            const username = prompt(translations[currentLanguage].enter_username);\n            if (!username) return;\n            \n            const password = prompt(translations[currentLanguage].enter_password);\n            if (!password) return;\n            \n            const users = {};\n            $('#user-table tbody tr').each(function() {\n                const tds = $(this).find('td');\n                users[$(tds[0]).text()] = $(tds[1]).text();\n            });\n            \n            users[username] = password;\n            \n            saveUsers(users);\n        }\n\n        function deleteUser(username) {\n            if (!confirm(translations[currentLanguage].confirm_delete_user)) return;\n            \n            const users = {};\n            $('#user-table tbody tr').each(function() {\n                const tds = $(this).find('td');\n                const user = $(tds[0]).text();\n                if (user !== username) {\n                    users[user] = $(tds[1]).text();\n                }\n            });\n            \n            saveUsers(users);\n        }\n\n        function saveUsers(users) {\n            $.ajax({\n                url: appendToken('/api/users'),\n                method: 'POST',\n                contentType: 'application/json',\n                data: JSON.stringify({ users: users }),\n                success: function(response) {\n                    if (response.status === 'success') {\n                        showMessage('users_save_success');\n                        loadUsers();\n                    } else {\n                        showMessage('users_save_failed', response.message);\n                    }\n                },\n                error: function(xhr, status, error) {\n                    showMessage('users_save_failed', error);\n                }\n            });\n        }\n\n        function editUserPassword(username) {\n            const newPassword = prompt(translations[currentLanguage].enter_new_password);\n            if (!newPassword) return;\n            \n            const users = {};\n            $('#user-table tbody tr').each(function() {\n                const tds = $(this).find('td');\n                const user = $(tds[0]).text();\n                users[user] = user === username ? newPassword : $(tds[1]).text();\n            });\n            \n            saveUsers(users);\n        }\n\n        $(document).ready(function() {\n            loadUsers();\n        });\n\n        searchInput.addEventListener('input', function() {\n            clearSearchBtn.style.display = this.value ? 'block' : 'none';\n            \n            if (searchTimeout) {\n                clearTimeout(searchTimeout);\n            }\n            \n            searchTimeout = setTimeout(() => {\n                updateLogs(true);\n            }, 300);\n        });\n        \n        clearSearchBtn.addEventListener('click', function() {\n            searchInput.value = '';\n            this.style.display = 'none';\n            updateLogs(true);\n        });\n\n        // 添加平滑滚动效果\n        document.querySelectorAll('a[href^=\"#\"]').forEach(anchor => {\n            anchor.addEventListener('click', function (e) {\n                e.preventDefault();\n                document.querySelector(this.getAttribute('href')).scrollIntoView({\n                    behavior: 'smooth'\n                });\n            });\n        });\n\n        // 添加加载动画\n        function showLoading() {\n            document.querySelector('.loading-overlay').style.display = 'flex';\n        }\n\n        function hideLoading() {\n            document.querySelector('.loading-overlay').style.display = 'none';\n        }\n\n        // 优化AJAX请求\n        function makeRequest(url, method = 'GET', data = null) {\n            showLoading();\n            return $.ajax({\n                url: appendToken(url),\n                method: method,\n                contentType: 'application/json',\n                data: data ? JSON.stringify(data) : null\n            }).always(hideLoading);\n        }\n\n        // 添加到现有的 JavaScript 代码中\n        function getRandomUser() {\n            const users = $('#user-table tbody tr').map(function() {\n                const username = $(this).find('td:first').text();\n                const password = $(this).find('td:eq(1)').text();\n                return `${username}:${password}`;\n            }).get();\n            \n            return users.length ? users[Math.floor(Math.random() * users.length)] : '';\n        }\n\n        function updateLocalAddresses() {\n            const port = $('input[name=\"port\"]').val() || '1080';\n            const randomAuth = getRandomUser();\n            const authString = randomAuth ? `${randomAuth}@` : '';\n            \n            const httpAddress = `http://${authString}127.0.0.1:${port}`;\n            const socks5Address = `socks5://${authString}127.0.0.1:${port}`;\n            \n            // 只在地址发生变化时更新显示\n            const currentHttp = $('#http-address').text();\n            const currentSocks5 = $('#socks5-address').text();\n            \n            if (currentHttp !== httpAddress) {\n                $('#http-address').text(httpAddress);\n            }\n            if (currentSocks5 !== socks5Address) {\n                $('#socks5-address').text(socks5Address);\n            }\n        }\n\n        function copyToClipboard(elementId) {\n            const text = $(`#${elementId}`).text();\n            navigator.clipboard.writeText(text).then(() => {\n                const btn = $(`#${elementId}`).next('.copy-btn');\n                const originalHtml = btn.html();\n                \n                btn.html('<i class=\"fa fa-check\"></i>');\n                btn.addClass('copy-success');\n                \n                setTimeout(() => {\n                    btn.html(originalHtml);\n                    btn.removeClass('copy-success');\n                }, 1500);\n            }).catch(err => {\n                console.error('复制失败:', err);\n            });\n        }\n\n        // 修改定时更新逻辑\n        let statusUpdateInterval;\n\n        function startStatusUpdate() {\n            // 清除现有的定时器\n            if (statusUpdateInterval) {\n                clearInterval(statusUpdateInterval);\n            }\n            \n            // 启动新的定时更新，降低更新频率\n            statusUpdateInterval = setInterval(updateStatus, 2000); // 改为2秒更新一次\n        }\n\n        // 添加输入框焦点事件处理\n        $(document).ready(function() {\n            // 输入框获得焦点时暂停状态更新\n            $('input, textarea').on('focus', function() {\n                if (statusUpdateInterval) {\n                    clearInterval(statusUpdateInterval);\n                }\n            });\n\n            // 输入框失去焦点时恢复状态更新\n            $('input, textarea').on('blur', function() {\n                startStatusUpdate();\n            });\n\n            // 其他初始化代码...\n        });\n\n        // 添加到现有的 JavaScript 代码中\n        function updateServiceStatus(status) {\n            const statusDot = document.querySelector('.status-dot');\n            const statusText = document.querySelector('.status-text');\n            \n            if (status === 'running') {\n                statusDot.classList.remove('stopped');\n                statusText.textContent = translations[currentLanguage].service_status.running;\n                \n                document.querySelector('.start-btn').disabled = true;\n                document.querySelector('.stop-btn').disabled = false;\n                document.querySelector('.restart-btn').disabled = false;\n            } else {\n                statusDot.classList.add('stopped');\n                statusText.textContent = translations[currentLanguage].service_status.stopped;\n                \n                document.querySelector('.start-btn').disabled = false;\n                document.querySelector('.stop-btn').disabled = true;\n                document.querySelector('.restart-btn').disabled = true;\n            }\n        }\n\n        // 分离按钮状态更新逻辑\n        function updateServiceButtons(status) {\n            const startBtn = document.querySelector('.start-btn');\n            const stopBtn = document.querySelector('.stop-btn');\n            const restartBtn = document.querySelector('.restart-btn');\n            \n            if (status === 'running') {\n                startBtn.disabled = true;\n                stopBtn.disabled = false;\n                restartBtn.disabled = false;\n            } else {\n                startBtn.disabled = false;\n                stopBtn.disabled = true;\n                restartBtn.disabled = true;\n            }\n        }\n\n        function checkVersion() {\n            $.get(appendToken('/api/version'), function(data) {\n                if (data.status === 'success') {\n                    $('#current-version').text(data.current_version);\n                    const versionStatus = $('#version-status');\n                    if (data.is_latest) {\n                        versionStatus.html(`<span class=\"text-success\"><i class=\"fa fa-check-circle\"></i> ${translations[currentLanguage].latest_version}</span>`);\n                    } else {\n                        versionStatus.html(`<span class=\"text-warning\"><i class=\"fa fa-exclamation-circle\"></i> ${translations[currentLanguage].new_version_available} (${data.latest_version})</span>`);\n                    }\n                } else {\n                    $('#version-status').html(`<span class=\"text-danger\"><i class=\"fa fa-times-circle\"></i> ${translations[currentLanguage].version_check_failed}</span>`);\n                }\n            }).fail(function() {\n                $('#version-status').html(`<span class=\"text-danger\"><i class=\"fa fa-times-circle\"></i> ${translations[currentLanguage].version_check_failed}</span>`);\n            });\n        }\n\n        // 在页面加载完成后调用版本检查\n        $(document).ready(function() {\n            checkVersion();\n            // 每30分钟检查一次版本更新\n            setInterval(checkVersion, 30 * 60 * 1000);\n        });\n\n        // 添加一个函数来更新UI状态\n        function updateUIState() {\n            const useGetip = $('input[name=\"use_getip\"]').prop('checked');\n            $('input[name=\"getip_url\"]').prop('disabled', !useGetip);\n        }\n\n        // 为 use_getip checkbox 添加变更事件监听器\n        $('input[name=\"use_getip\"]').on('change', function() {\n            updateUIState();\n        });\n\n        function showNotification(message, type = 'success') {\n            const container = document.getElementById('notification-container');\n            const notification = document.createElement('div');\n            notification.className = `notification ${type}`;\n            \n            const icon = document.createElement('i');\n            icon.className = `fa fa-${type === 'success' ? 'check-circle' : 'exclamation-circle'}`;\n            \n            const messageElement = document.createElement('p');\n            messageElement.className = 'notification-message';\n            messageElement.textContent = message;\n            \n            notification.appendChild(icon);\n            notification.appendChild(messageElement);\n            container.appendChild(notification);\n            \n            // 3秒后自动移除通知\n            setTimeout(() => {\n                notification.style.animation = 'fadeOut 0.3s ease-out';\n                setTimeout(() => {\n                    container.removeChild(notification);\n                }, 300);\n            }, 3000);\n        }\n\n        function controlService(action) {\n            $.ajax({\n                url: appendToken('/api/service'),\n                method: 'POST',\n                contentType: 'application/json',\n                data: JSON.stringify({ action: action }),\n                success: function(response) {\n                    if (response.status === 'success') {\n                        showNotification(translations[currentLanguage][`service_${action}_success`], 'success');\n                        updateStatus();\n                    } else {\n                        showNotification(translations[currentLanguage].operation_failed.format(response.message), 'error');\n                    }\n                },\n                error: function(xhr) {\n                    showNotification(translations[currentLanguage].operation_failed.format(xhr.responseText), 'error');\n                }\n            });\n        }\n\n        function updateServiceStatus(status) {\n            const statusDot = document.querySelector('.status-dot');\n            const statusText = document.querySelector('.status-text');\n            \n            if (status === 'running') {\n                statusDot.style.backgroundColor = '#2ecc71';\n                statusText.textContent = '运行中';\n            } else {\n                statusDot.style.backgroundColor = '#e74c3c';\n                statusText.textContent = '已停止';\n            }\n        }\n    </script>\n</body>\n</html>\n"
  }
]